mysql行鎖設計

來源:互聯網
上載者:User

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

SELECT  restnum FROM book WHERE id =1  ;   --如果restnum大於0,執行update 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引擎可以通過索引對資料行加鎖。以上借書的語句變為:

BEGIN; SELECT restnum FROM book WHERE id =1 FOR UPDATE  ; -- 給id=1的行加上排它鎖且id有索引 UPDATE  book SET restnum=restnum-1 WHERE  ; Commit;

  

   這樣,第二個人執行到select語句的時候就會處於等待狀態直到第一個人執行commit。從而保證了第二個人不會讀到第一個人修改前的資料。 那這樣是不是萬無一失了呢,答案是否定的。看下面的例子。

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

CREATE TABLE 'book' (   'id' INT(11) NOT NULL AUTO_INCREMENT,   'num' INT(11) DEFAULT NULL,   'name' VARCHAR(0) DEFAULT NULL,   PRIMARY KEY ('id'),   KEY 'asd' ('num') ) ENGINE=InnoDB DEFAULT CHARSET=gbk

  

  其中num欄位加了索引

  然後插入資料,運行,

INSERT INTO book(num) VALUES(11),(11),(11),(11),(11); 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都關閉,然後運行:

DELETE FROM book WHERE num=11 LIMIT 3; 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 的行鎖用起來並不是那麼隨心所欲的,必須要考慮索引。再看下面的例子。

SELECT id FROM items WHERE id IN (SELECT id FROM items WHERE id < 6) FOR UPDATE; --id欄位加了索引 SELECT id FROM items WHERE id IN (1,2,3,4,5) FOR UPDATE;

  

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

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

SET sql_mode= 'STRICT_TRANS_TABLES,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION'; BEGIN; SELECT restnum FROM book WHERE id =1   ; --取消排它鎖, 設定restnum為unsigned UPDATE  book SET restnum=restnum-1 WHERE  ; IF(UPDATE執行成功) commit; ELSE  ROLLBACK;

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

原文地址:http://www.searchtb.com/2010/09/mysql%E8%A1%8C%E9%94%81%E6%B7%B1%E5%85%A5%E7%A0%94%E7%A9%B6-2.html

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.