高并發(fā)加鎖同步及主鍵非主鍵混用死鎖問題研究

場景介紹晒喷,用戶有100塊錢孝偎,同時給100個人轉(zhuǎn)賬,每個收款用戶1快錢凉敲。在高并發(fā)下衣盾,付款方扣減金額時寺旺,客戶資金安全不可控制,且容易發(fā)生死鎖势决。

數(shù)據(jù)表:賬戶表account阻塑、流水表txnlog

create table account (
    'custname' varchar(32) not null comment '姓名',
    'acct' varchar(32) not null comment '賬號',
    'amt' bigdecimal not null comment '賬戶金額',
    'identify' varchar(32) not null comment '證件號',
    'identifynum' varchar(32) not null comment '證件類型'
    primary key 'acct',
    unique index idx('identify','identifynum'),
    unique index idx_name('custname')
)
comment='賬戶表'
collate='uft8_bin'
engine='InnoDB'
)
create table txnlog (
    'id' int(11) not null comment 'id',
    'custname' varchar(32) not null comment '姓名',
    'acct' varchar(32) not null comment '賬號',
    'amt' bigdecimal not null comment '賬戶金額',
    'skacct' varchar(32) not null comment '收款賬號',
    'skcustname' varchar(32) not null comment '收款姓名'
    primary key 'id',
)
comment='流水表'
collate='uft8_bin'
engine='InnoDB'

高并發(fā)資金安全問題分析

兩個轉(zhuǎn)賬請求同時到達(dá)并處理,對應(yīng)A果复、B兩個事務(wù)陈莽。存在問題:臟讀、可重復(fù)讀据悔、不可重復(fù)讀(官方定義為幻像/幻讀問題)传透、丟失更新。

事務(wù) 動作
事務(wù)A 1极颓、查詢賬戶表賬戶金額朱盐;
2、扣減賬戶金額更新賬戶表菠隆;
3兵琳、生成轉(zhuǎn)出流水;
4骇径、查詢賬戶表賬戶金額
事務(wù)B 1躯肌、查詢賬戶表賬戶金額;
2破衔、扣減賬戶金額更新賬戶表清女;
3、生成轉(zhuǎn)出流水晰筛;
4嫡丙、查詢賬戶表賬戶金額

A、B事務(wù)的處理順序如下 :

步驟 A B
1 查詢賬戶金額
2 查詢賬戶金額
3 扣減賬戶金額更新賬戶表
4 生成轉(zhuǎn)出流水
5 查詢賬戶金額
6 扣減賬戶金額更新賬戶表
7 生成轉(zhuǎn)出流水
8 查詢賬戶金額

innodb存儲引擎默認(rèn)隔離級別repeatable read(可重復(fù)讀)读第,解決了臟讀問題曙博,保證了在同一事務(wù)中,多次讀取同樣事務(wù)的結(jié)果是一致的怜瞒。 但是理論上父泳,可重復(fù)讀隔離級別還是無法解決另一個幻讀(phantom read)問題。所謂幻讀吴汪,指當(dāng)前事務(wù)在讀取某個范圍內(nèi)的記錄時惠窄,另一個事務(wù)又在該范圍內(nèi)插入了新的記錄。當(dāng)之前的事務(wù)又在該范圍內(nèi)插入了新的記錄漾橙,會產(chǎn)生幻行杆融。更通俗點(diǎn),phantom problem是指在同一事務(wù)下近刘,連續(xù)執(zhí)行兩次同樣的sql語句可能導(dǎo)致不同的結(jié)果擒贸,第二次的sql語句可能會返回之前不存在的行。A中若增加統(tǒng)計(jì)流水總數(shù) select count(*) from txnLog;觉渴,可模擬出幻讀問題 介劫。 為解決幻讀問題,innodb存儲引擎引入了多版本并發(fā)控制(MVCC)解決幻讀問題案淋。

附:mvcc的實(shí)現(xiàn)座韵,是通過保存數(shù)據(jù)在某個時間點(diǎn)的快照來實(shí)現(xiàn)的,也就是說踢京,不管需要執(zhí)行多長時間誉碴,每個事務(wù)內(nèi)看到的數(shù)據(jù)都是一致的。根據(jù)事務(wù)開始的時間不同瓣距,每個事務(wù)對同一張表黔帕,同一時刻看到的數(shù)據(jù)可能是不一樣的。 innodb的MVCC蹈丸,是通過在每行記錄后面保存兩個隱藏的列來實(shí)現(xiàn)的成黄。這兩個列,一個保存了行的創(chuàng)建時間逻杖,一個保存了行的過期時間(或刪除時間)奋岁,當(dāng)然存儲的并不是實(shí)際的時間值,而是系統(tǒng)版本號荸百,每開始一個新的事務(wù)闻伶,系統(tǒng)版本號都會自動遞增。事務(wù)開始時刻的系統(tǒng)版本號會作為事務(wù)的版本號够话,用來和查詢到的每行記錄的版本號進(jìn)行比較蓝翰。

innodb存儲引擎默認(rèn)隔離級別repeatable read (可重復(fù)讀),mvcc的具體操作:

  • select更鲁, innodb會個根據(jù)以下兩個條件檢查每行記錄霎箍。 1、innodb只查找版本早于當(dāng)前事務(wù)版本的數(shù)據(jù)行(行的系統(tǒng)版本號小于或等于事務(wù)的系統(tǒng)版本號)澡为,這樣可以確保事務(wù)讀取的行漂坏,要么是在事務(wù)開始前已經(jīng)存在,要么是事務(wù)自身插入或者修改過的媒至。 2顶别、行的刪除版本要么未定義(未執(zhí)行過刪除操作),要么大于當(dāng)前事務(wù)版本號拒啰,可確保事務(wù)讀取到的行驯绎,在事務(wù)開始之前未被刪除。
  • insert , innodb為新插入的每一行保存當(dāng)前系統(tǒng)版本號作為行版本號
  • delete谋旦, innodb為刪除的每一行保存當(dāng)前系統(tǒng)版本號作為行刪除標(biāo)示
  • update剩失, innodb為插入一行新數(shù)據(jù)屈尼,保存當(dāng)前系統(tǒng)版本號作為行版本號,同時保存當(dāng)前系統(tǒng)版本號到原來的行作為行刪除標(biāo)示拴孤。(更新一條記錄脾歧,分為兩步,新增一條記錄演熟,同時更新原記錄的刪除標(biāo)示)

在這個場景中鞭执,由于mvcc的作用,A無法看到B的修改芒粹,A中扣減賬戶金額是在A開始的快照上進(jìn)行的兄纺。若A修改成功,會導(dǎo)致B對賬戶表的修改丟失化漆。

解決方案:
  • 方案一估脆,修改隔離級別為read committed(提交讀),一個事務(wù)開始時座云,只能看見已經(jīng)提交的事務(wù)所做的修改旁蔼。即A事務(wù)能看到B事務(wù)的修改。 生產(chǎn)環(huán)境不允許修改隔離級別疙教,本方案不可用
  • 方案二棺聊,接受innodb默認(rèn)隔離級別repeatable read(可重復(fù)讀),因innodb存儲引擎采用了next-key locking的算法避免phantom problem贞谓,如select * from t where a>2 for update限佩,其鎖住的是(2,+無窮)這個范圍加了X鎖裸弦,任何對這個范圍的插入都是不允許的祟同,從而避免phantom problem。 對于賬戶表理疙、流水表在一個事務(wù)中的操作晕城。第一步對賬戶表加X鎖(select * from account where 賬號='123456' for update),后續(xù)再進(jìn)行賬戶修改窖贤、流水修改砖顷。 使得對同一賬戶的操作,其他事務(wù)需要等待X鎖赃梧。此外滤蝠,對于不可變值的查詢,可以提到事務(wù)開始前進(jìn)行授嘀。

并發(fā)下innodb主鍵索引與非主鍵索引同時使用死鎖問題分析

情景一物咳、A事務(wù),主鍵索引取的X鎖蹄皱,使用非主鍵索引更新览闰。B事務(wù)芯肤,使用非主鍵索引更新,如下

步驟 A B
1 主鍵索引
set autocommit=false;
begin;
select * from account where acct='123456' for update;
2 非主鍵索引
set autocommit=false;
begin;
update account set amt=amt+1 where identify='456789' and identifynum='01';
3 非主鍵索引
update account set amt=amt+1 where identify='456789' and identifynum='01' ;
4 mysql>update account set amt=amt+1 where identify='456789' and identifynum='01' ;

ERROR 1213 (40001):Deadlock found when trying to get lock; try restarting transaction

執(zhí)行到A3压鉴,B4會話報(bào)錯死鎖纷妆。

  • 之所以會造成B死鎖,在innodb存儲引擎下晴弃,行級鎖并不是直接鎖記錄,而是鎖索引逊拍。
  • A1 的sql語句用的主鍵作為條件上鞠,鎖的是主鍵索引,A事務(wù)占用了主鍵芯丧。
  • B2的sql語句芍阎,用的是非主鍵作為條件,mysql會先鎖非主鍵索引缨恒,再鎖主鍵索引谴咸,此時B事務(wù)占用了非主鍵索引,等待主鍵索引骗露。
  • A3的sql語句岭佳,使用了非主鍵索引進(jìn)行update操作,會等待B的非主鍵索引釋放萧锉。A/B兩個事務(wù)間形成死鎖珊随。
  • innodb通過死鎖檢測機(jī)制wait-for graph 等待圖, 檢測到死鎖回路柿隙,選擇回滾undo量最小的事務(wù)B叶洞,對其進(jìn)行回滾,讓A繼續(xù)執(zhí)行禀崖。

解決方案:

  • 1衩辟、在所有賬戶表修改操作開始前,使用主鍵索引對賬戶表加鎖 select * from account where acct='123456' for update波附。 再進(jìn)行賬戶修改艺晴、流水操作、其他掸屡。
  • 2财饥、對賬戶表的查詢和修改,統(tǒng)一使用主鍵索引或非主鍵索引折晦。避免主鍵索引與非主鍵索引共用時造成的死鎖钥星。
    附:測試代碼中死鎖的發(fā)生,1满着、A會話為eclipse編譯器執(zhí)行谦炒,代碼 select * from account where acct='123456' for update 下一行打斷點(diǎn)贯莺,執(zhí)行到斷點(diǎn)出。 2宁改、打開mysql的會話窗口缕探,執(zhí)行B2。 3还蹲、eclipse繼續(xù)往下走爹耗,A3執(zhí)行后,查看B會話被中斷谜喊,能看到死鎖提示潭兽。

死鎖是指兩個或兩個以上的事務(wù)在執(zhí)行過程中,因爭奪鎖資源而造成的一種互相等待的現(xiàn)象斗遏。解決死鎖問題最簡單的方法是超時山卦,即兩個事務(wù)互相等待時,當(dāng)一個等待時間超過設(shè)置的某一閾值诵次,其中一個事務(wù)進(jìn)行回滾账蓉,另一個等待的事務(wù)就能繼續(xù)進(jìn)行。但是從上述案例中逾一,并不是等待超時铸本,而是死鎖后直接觸發(fā)了事務(wù)回滾,顯然不是等待超時的解決方案遵堵。
innodb存儲引擎采用wait-for graph(等待圖)的方式來進(jìn)行死鎖檢測归敬,較之超時的方案,這是一種更為主動的死鎖檢測方式鄙早。在每個事務(wù)請求鎖并發(fā)生等待時都會判斷是否存在回路汪茧,若存在則有死鎖,通常來說innodb存儲引擎會選擇回滾undo量最小的事務(wù)限番。

情景二舱污、模擬兩個非主鍵索引 unique index idx('identify','identifynum')、unique index idx_name('custname') 混用發(fā)生死鎖弥虐,

步驟 A B
1 主鍵索引
set autocommit=false;
begin;
update account set amt=amt+1 where custname=‘張三’;
2 非主鍵索引 identify + identifynum
set autocommit=false;
begin;
update account set amt=amt+1 where identify ='45678' and identifynum='01';
3 非主鍵索引 custname

update account set amt=amt+1 where identify ='45678' and identifynum='01';
4 死鎖中斷
mysql>update account set amt=amt+1 where identify='456789' and identifynum='01' ;

ERROR 1213 (40001):Deadlock found when trying to get lock; try restarting transaction

死鎖發(fā)生過程分析

  • A1獲取了非主鍵索引custname扩灯,然后獲取了主鍵索引acct
  • B2獲取了非主鍵索引identify + identifynum ,等待主鍵索引acct
  • A3需要獲取非主鍵索引identify + identifynum霜瘪,但該索引已被B獲取珠插,A3等待非主鍵索引identify + identifynum的釋放,死鎖發(fā)生颖对。
  • innodb的死鎖檢測機(jī)制wait-for graph 等待圖捻撑,檢測到鎖等待回路,判斷死鎖發(fā)生,選擇回滾undo量最小的事務(wù)顾患。innodb判斷B為undo最小的事務(wù)番捂,中斷并回滾B事務(wù)。A事務(wù)繼續(xù)往下執(zhí)行江解。
情景三设预、模擬A、B會話均為相同主鍵索引
步驟 A B
1 主鍵索引
set autocommit=false;
begin;
select * from account where acct='123456' for update;
2 主鍵索引
set autocommit=false;
begin;
update account set amt=amt+1 where acct='123456';
3 停下來不繼續(xù)往下走
4 等待超時
mysql>update account set amt=amt+1 where acct='123456';

ERROR 1205 (HY000):Lock wait timeout exceeded; try restarting transaction

A3停下來犁河,不往下走鳖枕,則B4會提示等待鎖超時,會話中斷桨螺。從A宾符、B會話都使用主鍵索引可知,會話只會等待鎖超時彭谁,不會立即中斷事務(wù)。因此在編碼中允扇,盡可能避免在對同一條記錄操作的事務(wù)中缠局,混用主鍵索引和非主鍵索引。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末考润,一起剝皮案震驚了整個濱河市狭园,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌糊治,老刑警劉巖唱矛,帶你破解...
    沈念sama閱讀 222,104評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異井辜,居然都是意外死亡绎谦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,816評論 3 399
  • 文/潘曉璐 我一進(jìn)店門粥脚,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窃肠,“玉大人,你說我怎么就攤上這事刷允≡┝簦” “怎么了?”我有些...
    開封第一講書人閱讀 168,697評論 0 360
  • 文/不壞的土叔 我叫張陵树灶,是天一觀的道長纤怒。 經(jīng)常有香客問我,道長天通,這世上最難降的妖魔是什么泊窘? 我笑而不...
    開封第一講書人閱讀 59,836評論 1 298
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上州既,老公的妹妹穿的比我還像新娘谜洽。我一直安慰自己,他們只是感情好吴叶,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,851評論 6 397
  • 文/花漫 我一把揭開白布阐虚。 她就那樣靜靜地躺著,像睡著了一般蚌卤。 火紅的嫁衣襯著肌膚如雪实束。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,441評論 1 310
  • 那天逊彭,我揣著相機(jī)與錄音咸灿,去河邊找鬼。 笑死侮叮,一個胖子當(dāng)著我的面吹牛避矢,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播囊榜,決...
    沈念sama閱讀 40,992評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼审胸,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了卸勺?” 一聲冷哼從身側(cè)響起砂沛,我...
    開封第一講書人閱讀 39,899評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎曙求,沒想到半個月后碍庵,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,457評論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡悟狱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,529評論 3 341
  • 正文 我和宋清朗相戀三年静浴,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片挤渐。...
    茶點(diǎn)故事閱讀 40,664評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡马绝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出挣菲,到底是詐尸還是另有隱情富稻,我是刑警寧澤,帶...
    沈念sama閱讀 36,346評論 5 350
  • 正文 年R本政府宣布白胀,位于F島的核電站椭赋,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏或杠。R本人自食惡果不足惜哪怔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,025評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧认境,春花似錦胚委、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,511評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至硼身,卻和暖如春硅急,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背佳遂。 一陣腳步聲響...
    開封第一講書人閱讀 33,611評論 1 272
  • 我被黑心中介騙來泰國打工营袜, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人丑罪。 一個月前我還...
    沈念sama閱讀 49,081評論 3 377
  • 正文 我出身青樓荚板,卻偏偏與公主長得像,于是被迫代替她去往敵國和親吩屹。 傳聞我的和親對象是個殘疾皇子跪另,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,675評論 2 359