MySQL行鎖小結

2021-06-18 14:40:08 字數 4232 閱讀 6464

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

select  restnum from book where id =

1 ; --如果restnum大於0,執行update

update book set restnum=restnum-

1where 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 =

1for

update ; -- 給id=1的行加上排它鎖且id有索引

update book set restnum=restnum-

1where ;

commit;

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

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

create

table

'book'

('id'

int(11)

notnull

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=

11for

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=

22for

update;

出現結果:

|

id| num | name ||16

|22| null ||17

|22| null ||18

|22| null ||19

|22| null ||20

|22| null |

5 rows in

set

update book set name=

'abc'

where num=

11;

━━━━━━━━━━━━━━━━

問題來了,session竟然處於等待狀態,可是num=11的行不是被第乙個session自己鎖住的麼,為什麼不能更新呢?好了,打這裡大家也許有自己的答案,先別急,再請看一下操作。

把2個session都關閉,然後執行:

delete

from book where num=

11limit3; 

delete

from book where num=

22limit

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-

1where ; if

(update執行成功) commit;

else

rollback;

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

mysql 行鎖 訂票 mysql 行鎖

在電子商務裡,經常會出現庫存數量少,購買的人又特別多,大併發情況下如何確保商品數量不會被多次購買.其實很簡單,利用事務 for update就可以解決.我們都知道for update實際上是共享鎖,是可以被讀取的.但是如何在執行時,不被讀取呢.簡單來說 假設現在庫存為1,現在有a和b同時購買 先開啟...

mysql行鎖詳解 詳解MySQL行鎖

鎖是計算機協調多個程序或執行緒併發訪問某一資源的機制。鎖保證資料併發訪問的一致性 有效性 鎖衝突也是影響資料庫併發訪問效能的乙個重要因素。鎖是mysql在伺服器層和儲存引擎層的的併發控制。mysql中從對資料操作的粒度分為表鎖和行鎖。表鎖是指對一整張表加鎖,一般是 ddl 處理時使用 而行鎖則是鎖定...

mysql行鎖的特性 MySql的表鎖行鎖及間隙鎖

常用命令 手動新增表鎖 lock table 表名稱 read write 表名稱2 read write 檢視表上加過的鎖 show open tables 刪除表鎖 unlock tables 1.表鎖 特點 1.每次操作鎖住整張表,開銷小,加鎖快 2.不會出現死鎖 3.鎖定粒度大,發生鎖衝突的...