在日常工作中,對一條數量值的增減操作很常見。
假設表結構如下:
create
table t_stock(
id int
notnull auto_increment comment '編號',
num int
notnull
default
0 comment '數量',
created_time datetime not
null
default
current_timestamp comment '建立時間',
updated_time datetime not
null
default
current_timestamp
onupdate
current_timestamp comment '修改時間',
primary
key (id)
)engine = innodb default charset = utf8 comment '庫存表';
初始化以下資料:
我們對資料進行增減有以下兩種方式:
#方法1---先查詢,程式計算後將結果值寫入資料庫
select num from t_stock where id = 5;
#now = num - 1
update t_stock set num = now where id = 5;
#方法2---直接在更新語句中計算結果並寫入
update t_stock set num = num -1
where id = 5
用springboot來模擬下併發情況下運**況,實現**如下:
//第一種方式
@transactional(rollbackfor = exception.class, isolation = isolation.repeatable_read, propagation = propagation.required)
public boolean modifystocktest3() catch (interruptedexception e)
jdbctemplate.update("update t_stock set num = " + pre + " where id = 5");
rs = jdbctemplate.queryformap("select num from t_stock where id = 5");
system.out
.println("pre : " + pre +" aft : " + (integer.parseint(rs.get("num").tostring())-1));
return true;
}//第二種方式
@transactional(rollbackfor = exception.class, isolation = isolation.repeatable_read, propagation = propagation.required)
public boolean modifystocktest4() catch (interruptedexception e)
jdbctemplate.update("update t_stock set num = num -1 where id = 5");
return true;
}
sleep是為了方便測試,下面為多執行緒測試**:
@test
public void test2() throws interruptedexception finally
}).start
(); }
countdownlatch.await
(); }
同時開300個執行緒進行操作,結果情況如下:
方式1:
通過控制台列印我們可以看出在執行過程中,資料更新產生了覆蓋。我們再看資料庫最終資料:
進行了300此減1,最終結果應該是200,但是現在為495。所以第一種方式會產生更新丟失,在併發情況下是行不通的。
在第二種方式下:
三百次操作後資料依舊正確。所以我們在對資料進行增減操作時,應該講資料計算放到sql中,因為在update的時候有加鎖操作,所以可以防止併發情況下產生的更新丟失問題。
還是對這個庫存表進行操作,比如要同時修改幾個商品的庫存,可以直接根據id修改庫存數量,但是在併發情況下可能會出現下面情況:
事務a : 需要更新記錄1、記錄2
事務b : 需要更新記錄1、記錄2
事務a開始執行 事務b開始執行
對記錄1 加鎖
對記錄2 加鎖
請求記錄2的鎖
請求記錄1的鎖
死鎖。。。
因為加鎖順序的不確定,可能會在兩個事務執行之間產生鎖的競爭然後導致死鎖,模擬**如下:
//事務a
@transactional(rollbackfor = exception.class, isolation = isolation.repeatable_read, propagation = propagation.required)
public boolean modifystocktest5() catch (interruptedexception e)
jdbctemplate.update("update t_stock set num = num -1 where id = 3");
system.out
.println("t5 3 -1");
return true;
}//事務b
@transactional(rollbackfor = exception.class, isolation = isolation.repeatable_read, propagation = propagation.required)
public boolean modifystocktest6() catch (interruptedexception e)
jdbctemplate.update("update t_stock set num = num -1 where id = 2");
system.out
.println("t6 2 -1");
return true;
}
同時修改id為2和3的庫存,但是事務a先修改id為2的記錄,事務b先修改id為3的記錄,接下來兩個事務就會請求對方的鎖,從而導致產生死鎖。
測試**:
@test
public void test3() throws interruptedexception finally
}).start
(); new
thread
(()-> finally
}).start
(); countdownlatch.await
(); }
執行上面**會發現報異常,提示deadlock。
解決辦法:
1、統一加鎖順序,例如按照id自然序來進行加鎖操作,這樣事務之間的加鎖操作就不會存在死鎖。
2、統一使用乙個全域性鎖,如在redis儲存乙個lock標記,當事務獲取到這個lock標記時,才允許進行更新操作,否則等待鎖。
資料庫 丟失更新
丟失更新是資料中乙個比較常見的經典問題,在做專案時我們有時可能會沒有注意到這個問題,但這個問題相當重要,有時會帶來比較嚴重的結果。下面我們就來討論下這個丟失更新。一 什麼是丟失更新 用乙個操作過程來說明 1 會話session1 中的乙個事務獲取 查詢 一行資料,並顯示給乙個使用者user1.2 會...
ORACLE 資料插入或者更新
在寫入資料的時候有時候需要根據資料庫中是否含有該條資料來判斷資料是插入還是更新,以下為oracle插入更新語法 單條資料錄入 select sys seq.nextval as id from dual merge into sys token a using select as id,as use...
vue blur v model資料沒有更新問題
今天遇到乙個問題,是乙個輸入框繫結了乙個失去焦點事件,要傳送乙個客戶填寫的資料給後台查詢然後拿到返回值把它渲染到頁面上,但是從後台獲取到的資料卻沒有在頁面上渲染出來,console.log列印時顯示資料已經變化成了從後台拿到的資料,卻沒有渲染到頁面上。看了官方文件之後發現,受 es5 的限制,vue...