一個比較見的業務場景,先從表裏讀一條數據的一列,然後在內存中計算該列的新值,最後再update新值到表裏:
select data from table where id = xxx; // db
newdate = data + 1; // 內存
update table set data = newdata where id = xxx; // db
這樣的三條語句,如果不加事務,可能會有更新丟失的問題。就是多個事務併發修改同一行數據時,讀到了相同的值,然後分別更新,那麼就會丟失前一次更新;
爲啥要分三條?直接update set data = data + 1貌似就行?
這裏的+1只是示例,實際上這個1可能需要根據一些特定的業務邏輯計算出來,所以必須要先讀到內存才能計算到新值;
怎麼解決更新丟失問題?
這裏介紹兩種方案:
1.使用事務+鎖定讀
begin;
select data from table where id = xxx for update; // db
newdate = data + 1; // 內存
update table set data = newdata where id = xxx; // db
commit;
這裏首先使用了begin語法,將上述三個步驟放入到事務中,並且讀取時使用了for update的鎖定讀方式,這樣一旦讀到了數據,那麼就會加X鎖,別的事務便無法讀該行數據或者修改該行數據了。
2.不使用事務,cas更新
// for loop begin
select data from table where id = xxx; // db
newdate = olddata + 1;// 內存
update table set data = newdata where id = xxx and data = olddata; // db
// for loop end
這個方案不需要事務,但是在update時,加上了cas的判斷,整套邏輯需要放在循環中完成,不斷cas嘗試直到更新成功,需要業務邏輯來處理cas更新,並且可能需要設置最大重試次數,防止一直自旋;
貌似在高併發場景下,使用方案一更好些,畢竟方案二衝突的概率比較大,一直重複select update也會影響性能;