在工作中遇到這樣一個場景,老師可以在多個課程中選擇自己可以上課的時間,但是當學生約老師的這個時間的時候就要求時間不可以有衝突(在相同的時間內只能有一個課程被約)
數據庫設計如下
appointment表
id | teacher_id | start_time | end_time | appoint_status |
---|---|---|---|---|
編號 | 老師編號 | 課程開始時間 | 課程結束時間 | 預約狀態(0:未約,1:已約) |
1 | 1 | 20181023 10:00:00 | 20181023 10:00:00 | 0 |
2 | 1 | 20181023 10:00:00 | 20181023 10:00:00 | 0 |
數據庫的中的初始數據是一個老師在倆個課程的相同時間下添加了可以預約的時間.
實現以及問題解決
開始實現的邏輯是這樣的
public void appointTeacher(int teacherId,int apponitmentId){
List<Appointment> appointment = 根據appointmentId到數據庫中查詢老師這個時間段的已約課程//代碼1
if(appointment.size()>0){//代碼2
throw new Exception("老師的這個時間已經有預約");
}eles{
根據appointmentId和原始狀態更新appoint_status;//代碼3
}
}
這樣的實現在串行化的執行中是沒有問題的,但是在併發的場景下就可能會出現問題。
當有倆個用戶同時預約這個老師不同課的同一個時間,就可能會出現以下的執行情況
倆個用戶幾乎同時操作,第一個請求(1,1),第二個(1,2)
服務器幾乎同時收到請求,創建倆個線程執行
第一個線程執行到了’代碼1’處,讀取數據庫。
第二個線程執行到了‘代碼1’處,讀取數據庫。
這時他們都沒有在數據庫中讀取到這個appointmentId所在的時間段內有已預約的課程
第一個線程執行到了’代碼2’處,判斷不成立。
第二個線程執行到了’代碼2’處,判斷不成立。
第一個線程執行到了’代碼3’處,更新數據庫。
第二個線程執行到了’代碼3’處,更新數據庫。
如果出現了這樣執行的情況,就會出現問題,老師的同一個時間被不同人,約了不同的課,那他去上那一節課呢?
爲了解決這個問題,我了想能不能在數據庫插入的時候判斷
於是我寫出了下面的sql
update appointment
set appoint_status = #{appointStatus}
where id=#{id}
and appoint_status = #{originalStatus}
and not exists (
select has
from (
SELECT 1 has
FROM
appointment a
WHERE ((a.start_time < #{endTime} and a.start_time >= #{startTime}) or (a.end_time <= #{endTime} and a.end_time > #{startTime})
or (#{startTime} >= a.start_time and #{startTime} < a.end_time) or (#{endTime} > a.start_time and #{endTime} <= a.end_time))
and a.teacher_id =#{teacherId}
and a.appoint_status = 1
) temp
)
我想在更新的時候進行判斷是否有衝突的時間應該就沒有問題了吧
但是我一想,也不對啊,在數據中這個一個查詢以及更新操作可能也不是原子的。這樣在查詢的時候也是一起執行,但是在執行更新的時候就還是會同時更新。
驗證
爲了驗證一下可靠性。於是我做了一個嘗試。
在navicate中開了倆個查詢窗口