1. 程式人生 > >mysql 行鎖小技巧 索引對行鎖的影響 批量update的時候容易出現死鎖

mysql 行鎖小技巧 索引對行鎖的影響 批量update的時候容易出現死鎖

錯誤:Deadlock found when trying to get lock; try restarting transaction

做專案時由於業務邏輯的需要,必須對資料表的一行或多行加入行鎖,舉個最簡單的例子,圖書借閱系統。假設 id=1 的這本書庫存為 1 ,但是有 2 個人同時來借這本書,此處的邏輯為

  1. Select   restnum  from  book  where  id =1 ;      
  2. -- 如果 restnum 大於 0 ,執行 update  
  3. Update   book  set restnum=restnum-1 where id=1 ;   

問題就來了,當 2 個人同時來借的時候,有可能第一個人執行 select 語句的時候,第二個人插了進來,在第一個人沒來得及更新 book 表的時候,第二個人查到資料了,其實是髒資料,因為第一個人會把 restnum 值減 1 ,因此第二個人本來應該是查到 id=1 的書 restnum 為 0 了,因此不會執行 update ,而會告訴它 id=1 的書沒有庫存 了,可是

資料庫哪懂這些,資料庫只負責執行一條條 SQL 語句,它才不管中間有沒有其他 sql 語句插進來,它也不知道要把一個 session 的 sql 語句執行完再執行另一個 session 的。因此會導致併發的時候 restnum 最後的結果為 -1 ,顯然這是不合理的,所以,才出現鎖的概念, MySQL 使用 innodb 引擎可以通過索引 對資料行加鎖。以上借書的語句變為:

  1. Begin;  
  2. Select   restnum  from  book  where  id =1  for   update ;  
  3. -- 給 id=1 的行加上排它鎖且 id 有索引  
  4. Update   book  set restnum=restnum-1 where id=1 ;  
  5. Commit;   

這樣,第二個人執行到 select 語句的時候就會處於等待狀態直到第一個人執行 commit 。從而保證了第二個人不會讀到第一個人修改前的資料。

那這樣是不是萬無一失了呢,答案是否定的。看下面的例子。

跟我一步一步來,先建立表 

  1. CREATE TABLE `book` (   
  2.   `id` int(11) NOT NULL auto_increment,   
  3.   `num` int(11) default NULL,   
  4.   `name` varchar(0) default NULL,   
  5.   PRIMARY KEY  (`id`),   
  6.   KEY `asd` (`num`)   
  7. ) ENGINE=InnoDB  DEFAULT CHARSET=gbk   

其中 num 欄位加了索引 

然後插入資料,執行, 
  1. insert into book(num) values(11),(11),(11),(11),(11);   
  2. insert into book(num) values(22),(22),(22),(22),(22);   

然後開啟 2 個 mysql 控制檯視窗,其實就是建立 2  session 做併發操作 

******************************************************************** 
在第一個 session 裡執行: 
begin; 
select * from book where num=11 for update; 
出現結果: 
+----+-----+------+ 
| id | num | name | 
+----+-----+------+ 
| 11 |  11 | NULL | 
| 12 |  11 | NULL | 
| 13 |  11 | NULL | 
| 14 |  11 | NULL | 
| 15 |  11 | NULL | 
+----+-----+------+ 
5 rows in set 

然後在第二個 session 裡執行: 
begin; 
select * from book where num=22 for update; 
出現結果: 
+----+-----+------+ 
| id | num | name | 
+----+-----+------+ 
| 16 |  22 | NULL | 
| 17 |  22 | NULL | 
| 18 |  22 | NULL | 
| 19 |  22 | NULL | 
| 20 |  22 | NULL | 
+----+-----+------+ 
5 rows in set 

好了,到這裡什麼問題都沒有,是吧,可是接下來問題就來了,大家請看: 
回到第一個 session ,執行: 
update book set name='abc' where num=11; 
******************************************************************************************** 
問題來了, session 竟然處於等待狀態 ,可是 num=11 的行不是被第一個 session 自己鎖住的麼,為什麼不能更新呢?好了,打這裡大家也許有自己的答案,先別急,再請看一下操作。 


把 2 個 session 都關閉,然後執行: 
  1. delete from book where num=11 limit 3;   
  2. delete from book where num=22 limit 3;   

其實就是把 num=11 和 22 的記錄各刪去 3 行, 
然後重複 “***********************” 之間的操作 
竟然發現,執行 update book set name='abc' where num=11; 後,有結果出現了,說明沒有被鎖住, 
這是為什麼呢,難道 2 行資料和 5 行資料, MySQL 來說,會產生鎖行和鎖表兩種情況嗎 。經過跟網友討論和翻閱資料,仔細分析後發現:

在以上實驗資料作為測試資料的情況下,由於 num 欄位重複率太高,只有 2 個值,分別是 11 和 12. 而資料量相對於這兩個值來說卻是比較大的,是 10 條, 5 倍的關係。 
那麼 mysql 在解釋 sql 的時候,會忽略索引,因為它的優化器發現:即使使用了索引,還是要做全表掃描,故而放棄了索引,也就沒有使用行鎖,卻使用了表鎖。 簡單的講,就是 MYSQL 無視了你的索引,它覺得與其行鎖,還不如直接表鎖,畢竟它覺得表鎖所花的代價比行鎖來的小。以上問題即便你使用了 force index 強制索引,結果還是一樣,永遠都是表鎖。

所以 mysql 的行鎖用起來並不是那麼隨心所欲的,必須要考慮索引。再看下面的例子。

  1. select id from items where id in (select id from items where id <6) for update;    
  2. --id欄位加了索引  
  3. select id from items where id in (1,2,3,4,5) for update;  

大部分會認為結果一樣沒什麼區別,其實差別大了,區別就是第一條 sql 語句會產生表鎖,而第二個 sql 語句是行鎖,為什麼呢?因為第一個 sql 語句用了子查詢外圍查詢故而沒使用索引,導致表鎖。

好了,回到借書的例子,由於 id 是唯一的,所以沒什麼問題,但是如果有些表出現了索引有重複值,並且 mysql 會強制使用表鎖的情況,那怎麼辦呢?一般來說只有重新設計表結構和用新的 SQL 語句實現業務邏輯,但是其實上面借書的例子還有一種辦法。請看下面程式碼:

  1. Set   sql_mode=  
  2. 'STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION';  
  3. Begin;  
  4. Select  restnum  from  book  where  id =1   ;  -- 取消排它鎖 , 設定 restnum 為 unsigned  
  5. Update   book  set restnum=restnum-1 where id=1 ;  
  6. If(update 執行成功 )  commit;  
  7. Else   rollback;   

上面是個小技巧,通過把資料庫模式臨時設定為嚴格模式,當 restnum 被更新為 -1 的時候,由於 restnum 是 unsigned 型別的,因此 update 會執行失敗,無論第二個session 做了什麼資料庫操作,都會被回滾,從而確保了資料的正確性,這個目的只是為了防止併發的時候極小概率出現的 2 個 session 的 sql 語句巢狀執行導致資料髒讀。當然最好的辦法還是修改表結構和 sql 語句,讓 MYSQL 通過索引來加行鎖, MySQL 測試版本為 5.0.75-log 和 5.1.36-community

參考連結:

http://blog.csdn.net/aesop_wubo/article/details/8286215

http://blog.csdn.net/fatshaw/article/details/52064668

相關推薦

mysql 技巧 索引影響 批量update的時候容易出現

錯誤:Deadlock found when trying to get lock; try restarting transaction 做專案時由於業務邏輯的需要,必須對資料表的一行或多行加入行鎖,舉個最簡單的例子,圖書借閱系統。假設 id=1 的這本書庫存為 1 

技巧textbox的

tchar subst void rom 功能 bstr dex 不知道 getline 沒什麽技術含量,但如果不知道則實現起來很麻煩。 c#中textbox.lines只記錄回車的數量,並不是真正的總行數,如何得到呢,請使用: int 總行數 = this.te

Vim 技巧-刪除末空格

在編寫程式碼時,我們時常一不留神就在行末多打了幾個空格。卻又看不出來,即使用 vim-airline 之類的外掛能知道有行末空格(trailing spaces),刪除起來也特別麻煩。本文就介紹一個我就常用的 vim 快捷鍵:刪除行末空格。 功能實現 Vim 並沒有內建的支援,所以我們有正則表示式替換來實現這

用Eclipse 統計程式碼技巧

今天公司SQA問我目前專案程式碼行數有多少,我當時就是想,以前好像寫過類似的統計工具但是一時又找不到 公司網路又不能下載,所以想想eclipse是不是又類似功能,找了下沒有,但突然一想有一個轉彎方法:統計工程裡面的\n個數 1. 按 CTRL+H 開啟查詢對話方塊 選擇fi

告訴你10個MySQL資料庫的技巧

無論是運維、開發、測試,還是架構師,資料庫技術是一個必備加薪神器,那麼,一直說學習資料庫、學MySQL,到底是要學習它的哪些東西呢? 1、如何快速掌握MySQL? 培養興趣   興趣是最好的老師,不論學習什麼知識,興趣都可以極大地提高學習效率。當然學習MySQL 5.6也不例外。   夯

mysql的儲存引擎innodb、myisam插入影響索引插入的影響

前言 一直好奇mysql的儲存引擎innodb和myisam對插入影響和索引對插入的影響。 這次我就來做個測試,以下測試供大家參考。 drop table userinfo; CREATE TAB

資料庫新增索引效能的影響以及使用場景

1.新增索引後查詢速度會變快   mysql中索引是儲存引擎層面用於快速查詢找到記錄的一種資料結構,索引對效能的影響非常重要,特別是表中資料量很大的時候,正確的索引會極大的提高查詢效率。簡單理解索引,就相當於一本磚頭厚書的目錄部分,通過目錄可以快速查詢到想要找的內容具體所在

mysql操作表時出現解決方式

情景:有時頻繁地某個表時,發現不能進行增刪改操作時,出現操作超時死鎖的情況 --顯示所有程序 show processlist kill id --顯示是否有死鎖 show   open tables where In_use > 0; --檢視死鎖 se

面試題:談談程序的理解?談談你執行緒的理解?2.程序的原因?如何解決程序

2.談談對程序的理解? 答:首先程序是指在系統中正在執行的一個應用程式;程式一旦執行就是程序,或者更專業化來說:程序是指程式執行時的一個例項,即它是程式已經執行到課中程度的資料結構的彙集。從核心的觀點看,程序的目的就是擔當分配系統資源(CPU時間、記憶體等)的基本單位,程序

關於兩個update語句互相的顯現,加深我們的瞭解

前段時間在msdn的論壇上看到鄒老大對一個問題的回覆,覺得對鎖更瞭解了,先二話不說“拿來”記錄學習下。 原帖地址:http://social.msdn.microsoft.com/Forums/zh-CN/6559504d-c546-45a6-89e2-eeb75041b3

發現操作系統的數據庫出現如何處理

ack cte 進行 username null amp 現在 res bstr where q.address = s.sql_addressand q.hash_value = s.sql_hash_valueand s.paddr = p.addrand exists

[經驗總結]呼叫WinSock的closesocket函數出現的解決辦法

       這兩天除錯一個網路應用程式,出現一個很詭異的問題:程式在關閉連線時失去響應。用Process Explorer工具檢視該程式的各個執行緒,發現一個工作執行緒的呼叫棧類似這樣: stopProc ==> closesocket ==> EnterCri

PL/SQL 出現解決辦法

在PL/SQL中操作資料表時,長時間沒反應,並且編輯某個表中資料時,出現“record is locked by another user”等情況,即出現了死鎖。 下面,簡述解決辦法: step1.P

Shell腳本編程技巧(1)-如何解決腳本中多重定向結束符不用齊到

shell 多行重定向 1、what?問題需求是什麽? 首先需求從何而來呢,主要是編寫shell腳本,用cat 進行多行輸入重定向的時候,結束符必須要對齊行首,格式不好看。 2、how?怎麽解決這個問題? 首先百度,google搜索了下,結果就是說的多的就是shell多行重定向沒有解決實際問題,可能是

總結自己使用shell命令經常使用到的8個技巧

技巧 span get ash lan host tab localhost 沒有 原創blog,轉載請註明出處 Shell是命令解釋器 [[email protected]/* */ ~]# cat /etc/shells 查看本系統共支持哪些shel

Sublime 技巧:文本自動換顯示?

toggle 文本 也有 word-wrap 實現 mman 喜歡 gin undefined Sublime Text tip for wrap line 題記:雖然現在寫代碼時,一般各種語言的規範都會說寫一行代碼不要超過好多好多字,如PEP8是79個字符,

微信程序---技巧省略符

logs pla isp pac nowrap 換行 lamp psi 程序 單行省略: overflow:hidden; text-overflow:ellipsis; white-space:nowrap; 多行省略: text-overflow:ellips

mysql中InnoDB存儲引擎的和表

nbsp 大於 依然 自帶 打折 一個 系統 指定 任務 Mysql的InnoDB存儲引擎支持事務,默認是行鎖。因為這個特性,所以數據庫支持高並發,但是如果InnoDB更新數據的時候不是行鎖,而是表鎖的話,那麽其並發性會大打折扣,而且也可能導致你的程序出錯。 而導致行鎖變為

mysql某個數據庫中表的數從大到排序

mysql 表的行數 排序 隨著公司的業務越來越大,工作中需要對某一個數據庫的表進行分表,為了做的更細致一點,在該數據庫中,將所有表,按行數從到小排序:實現方式:mysql> use information_schema;Reading table information for compl

How Javascript works (Javascript工作原理) (二) 引擎,運時,如何在 V8 引擎中書寫最優代碼的 5 條技巧

tco master 一次 指定 ava 技術分享 將不 創建 跳轉 個人總結: 一個Javascript引擎由一個標準解釋程序,或者即時編譯器來實現。 解釋器(Interpreter): 解釋一行,執行一行。 編譯器(Compiler): 全部編譯成機器碼,統一執行。(減