最近做專案時,將原先單條插入更新資料庫時改為批量插入更新。這樣做的好處是降低了qps(sql語句的數量),但是同時也帶來乙個問題,db的行鎖急劇增加。 由於批量更新執行時間長,導致資源被長時間鎖定,從而導致了大量的死鎖產生,即出現以下錯誤資訊: deadlock found when trying to get lock; try restarting transaction 借這個機會,研究一下資料庫死鎖的問題。
一. 什麼是資料庫死鎖?學過作業系統的人都知道,只有在併發的情況下,才會發生死鎖。 下面的圖可以形象地說明死鎖的形成,四輛車在乙個環形車道上行駛,如果沒有外力作用,這4輛車將無法執行。
死鎖發生在當多個程序訪問同一資料庫時,其中每個程序擁有的鎖都是其他程序所需的,由此造成每個程序都無法繼續下去。 簡單的說,程序a等待程序b釋放他的資源,b又等待a釋放他的資源,這樣就互相等待就形成死鎖。 產生死鎖的四個必要條件是: 1)互斥條件:指程序對所分配到的資源進行排它性使用,即在一段時間內某資源只由乙個程序占用。如果此時還有其它程序請求資源,則請求者只能等待,直至占有資源的程序用畢釋放。 2)請求和保持條件:指程序已經保持至少乙個資源,但又提出了新的資源請求,而該資源已被其它程序占有,此時請求程序阻塞,但又對自己已獲得的其它資源保持不放。 3)不剝奪條件:指程序已獲得的資源,在未使用完之前,不能被剝奪,只能在使用完時由自己釋放。 4)環路等待條件:指在發生死鎖時,必然存在乙個程序——資源的環形鏈,即程序集合中的p0正在等待乙個p1占用的資源;p1正在等待p2占用的資源,……,pn正在等待已被p0占用的資源。 這四個條件缺一不可。
二. 資料庫死鎖檢測我們mysql用的儲存引擎是innodb,從列印的錯誤日誌來看,innodb主動探知到死鎖,並回滾了某一苦苦等待的事務。那麼innodb是怎麼探知死鎖的? 直觀方法是在兩個事務相互等待時,當乙個等待時間超過設定的某一閥值時,對其中乙個事務進行回滾,另乙個事務就能繼續執行。這種方法簡單有效,在innodb中,引數innodb_lock_wait_timeout用來設定超時時間。 僅用上述方法來檢測死鎖太過被動,innodb還提供了wait-for graph演算法來主動進行死鎖檢測,每當加鎖請求無法立即滿足需要並進入等待時,wait-for graph演算法都會被觸發。 我們怎麼知道上圖中四輛車是死鎖的?他們相互等待對方的資源,而且形成環路!我們將每輛車看為乙個節點,當節點1需要等待節點2的資源時,就生成一條有向邊指向節點2,最後形成乙個有向圖。我們只要檢測這個有向圖是否出現環路即可,出現環路就是死鎖!這就是wait-for graph演算法。
innodb將各個事務看為乙個個節點,資源就是各個事務占用的鎖,當事務1需要等待事務2的鎖時,就生成一條有向邊從1指向2,最後行成乙個有向圖。
三.innodb鎖原理我們知道,innodb最大的貢獻就是支援了事務和行鎖,由於鎖的粒度更細,所以能更好的支援併發。 innodb實現了以下兩種型別的行鎖: 共享鎖(s):允許乙個事務去讀一行,阻止其他事務獲得相同資料集的排他鎖。 排他鎖(x):允許獲得排他鎖的事務更新資料,阻止其他事務取得相同資料集的共享讀鎖和排他寫鎖。 另外,為了允許行鎖和表鎖共存,實現多粒度鎖機制,innodb還有兩種內部使用的意向鎖(intention locks),這兩種意向鎖都是表鎖。 意向鎖是innodb自動加的,不需使用者干預。這裡不做過多分析。 對於update、delete和insert語句,innodb會自動給涉及的資料集加排他鎖(x); 對於普通select語句,innodb不會加任何鎖; 但是select語句可以顯式的加共享鎖和排他鎖。 ·共享鎖(s):select * from table_name where ... lock in share mode。 ·排他鎖(x):select * from table_name where ... for update。 用select ... in share mode獲得共享鎖,主要用在需要資料依存關係時來確認某行記錄是否存在,並確保沒有人對這個記錄進行update或者delete操作。 但是如果當前事務也需要對該記錄進行更新操作,則很有可能造成死鎖,對於鎖定行記錄後需要進行更新操作的應用,應該使用select... for update方式獲得排他鎖。
在5.5中,information_schema 庫中增加了三個關於鎖的表: innodb_trx ## 當前執行的所有事務 innodb_locks ## 當前出現的鎖 innodb_lock_waits ## 鎖等待的對應關係 innodb_locks記錄了當前的鎖的資訊,其表的結構依次如下:
a) lock_id:鎖的id以及被鎖住的空間id編號、頁數量、行數量 b) lock_trx_id:鎖的事務id。 c) lock_mode:鎖的模式。 d) lock_type:鎖的型別,表鎖還是行鎖 e) lock_table:要加鎖的表。 f) lock_index:鎖的索引。 g) lock_space:innodb儲存引擎表空間的id號碼 h) lock_page:被鎖住的頁的數量,如果是表鎖,則為null值。 i) lock_rec:被鎖住的行的數量,如果表鎖,則為null值。 j) lock_data:被鎖住的行的主鍵值,如果表鎖,則為null值。 鎖與索引的關係 假設我們有一張訊息表(msg),裡面有3個字段。假設id是主鍵,token是非唯一索引,message沒有索引。 create table msg ( id int, token int, message varchar(100), primary key(id), index(token) ) innodb對於主鍵使用了聚簇索引,這是一種資料儲存方式,表資料是和主鍵一起儲存,主鍵索引的葉結點儲存行資料。 對於普通索引,其葉子節點儲存的是主鍵值。 插入幾條資料: insert into msg values (1,20,'abc1'); insert into msg values (2,21,'abc2'); insert into msg values (3,22,'abc3'); insert into msg values (4,23,'abc4'); insert into msg values (5,21,'abc2'); 聚簇索引的儲存結構如下所示:idtokenmessage
120abc1
221abc2
322abc3
423abc4
521abc2
因為token是普通索引,即二級索引,其儲存結構如下,節點是主鍵值:tokenid
下面分析下索引和鎖的關係。
1.delete from msg where id=2; 由於id是主鍵,因此直接鎖住整行記錄即可,會對該行加x鎖。
2. delete from msg where token=21; 由於token是二級索引,因此首先鎖住二級索引(兩行),接著會鎖住相應主鍵所對應的記錄;
3. delete from msg where message='abc1'; message沒有索引,所以走的是全表掃瞄過濾。這時表上的各個記錄都將新增上x鎖。
從上面的分析可以得出結論: innodb的行級鎖並不是直接鎖記錄,而是鎖索引; 如果一條sql語句用到了主鍵索引,mysql會鎖住主鍵索引; 如果一條語句操作了非主鍵索引,mysql會先鎖住非主鍵索引,再鎖定主鍵索引。
四.死鎖成因了解了innodb鎖的基本原理後,下面分析下死鎖的成因。如前面所說,死鎖一般是事務相互等待對方資源,最後形成環路造成的。下面簡單講下造成相互等待最後形成環路的例子。 一般情況只發生鎖超時,就是乙個程序需要訪問資料庫表或者欄位的時候,另外乙個程式正在執行帶鎖的訪問(比如修改資料),那麼這個程序就會等待,當等了很久鎖還沒有解除的話就會鎖超時,報告乙個系統錯誤,拒絕執行相應的sql操作。 發生死鎖的情況比較少,比如乙個程序需要訪問兩個資源(資料庫表或者字段),當獲取乙個資源的時候程序就對它執行鎖定,然後等待下乙個資源空閒,這時候如果另外乙個程序也需要兩個資源,而已經獲得並鎖定了第二個資源,那麼就會死鎖,因為當前程序鎖定第乙個資源等待第二個資源,而另外乙個程序鎖定了第二個資源等待第乙個資源,兩個程序都永遠得不到滿足。 在本次專案中造成死鎖的原因:相同表記錄行鎖衝突 這種情況比較常見,兩個事務在執行資料批量更新時,事務a處理的的id列表為[1,2,3,4],而事務b處理的id列表為[8,9,10,4,2],這樣就造成了死鎖。
a和b在互相等待對方資源的過程中,形成了死鎖。
五. 如何盡可能避免死鎖1)以固定的順序訪問表和行。比如對兩個job批量更新的情形,簡單方法是對id列表先排序,後執行,這樣就避免了交叉等待鎖的情形;將兩個事務的sql順序調整為一致,也能避免死鎖。 2)大事務拆小。大事務更傾向於死鎖,如果業務允許,將大事務拆小。 3)在同乙個事務中,盡可能做到一次鎖定所需要的所有資源,減少死鎖概率。 4)降低隔離級別。如果業務允許,將隔離級別調低也是較好的選擇,執行提交讀允許事務讀取另乙個事務已讀取(未修改)的資料,而不必等待第乙個事務完成。使用較低的隔離級別(例如提交讀)而不使用較高的隔離級別(例如可序列讀)可以縮短持有鎖的時間,從而降低了鎖定爭奪。 5)為表新增合理的索引。可以看到如果不走索引將會為表的每一行記錄新增上鎖,死鎖的概率大大增大。
資料庫死鎖
1.死鎖的概念 死鎖是程序死鎖的簡稱,是由dijkstra於1965年研究銀行家演算法時首先提出來的。它是計算機作業系統乃至併發程式設計中最難處理的問題之一。實際上,死鎖問題不僅在計算機系統中存在,在我們日常生活中它也廣泛存在。我們先看看這樣乙個生活中的例子 在一條河上有一座橋,橋面較窄,只能容納一...
資料庫死鎖
資料庫在進行insert,update,delete這些更新操作的時候為了保證資料一致性都會使用排他鎖。乙個事務裡進行update操作,在事務結束之前 commit or rollback 排他鎖不會被釋放。因此在乙個事務裡update多條資料的時候執行順序就尤為重要,兩個併發事務中更新操作的執行順...
資料庫死鎖
死鎖 所謂死鎖 是指兩個或兩個以上的程序在執行過程中,因爭奪資源而造成的一種互相等待的現象,若無外力作用,它們都將無法推進下去.此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的程序稱為死鎖程序 由於資源占用是互斥的,當某個程序提出申請資源後,使得有關程序在無外力協助下,永遠分配不到必需的...