新聞中心
許多語言和工具都通過鎖,來保證并發(fā)場景下數(shù)據(jù)和邏輯的正確性,MySQL 也不例外。除了行鎖、表鎖這種范圍粒度外,還有這種針對讀和寫的 S鎖共享鎖 和 X鎖獨(dú)占鎖。

創(chuàng)新互聯(lián)是一家從事企業(yè)網(wǎng)站建設(shè)、成都網(wǎng)站建設(shè)、成都做網(wǎng)站、行業(yè)門戶網(wǎng)站建設(shè)、網(wǎng)頁設(shè)計(jì)制作的專業(yè)網(wǎng)站設(shè)計(jì)公司,擁有經(jīng)驗(yàn)豐富的網(wǎng)站建設(shè)工程師和網(wǎng)頁設(shè)計(jì)人員,具備各種規(guī)模與類型網(wǎng)站建設(shè)的實(shí)力,在網(wǎng)站建設(shè)領(lǐng)域樹立了自己獨(dú)特的設(shè)計(jì)風(fēng)格。自公司成立以來曾獨(dú)立設(shè)計(jì)制作的站點(diǎn)近千家。
隨著鎖定范圍的不同,鎖與鎖之間的互相影響也差異很大,這一點(diǎn)很好理解。比如一個(gè)操作加了表鎖之后,另一個(gè)想加行鎖就得等待;而一個(gè)行鎖一般并不會影響鎖另一行的行鎖。
除了書本上和八股文,你有沒有遇到過這些鎖相關(guān)的問題呢?
我先來說一個(gè)最近遇到的。
現(xiàn)象
某天,項(xiàng)目出現(xiàn)幾條監(jiān)控報(bào)警,都是在寫庫的時(shí)候獲取鎖超時(shí)導(dǎo)致。業(yè)務(wù)會在某種特定的場景下,出現(xiàn)如下的 MySQL 獲取鎖超時(shí),事務(wù)回滾的異常。
org.springframework.dao.CannotAcquireLockException:
### Error updating database.
Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: xxx
Lock wait timeout exceeded; try restarting transaction
看了下錯誤對應(yīng)的日志,發(fā)現(xiàn)當(dāng)時(shí) MySQL 要執(zhí)行一條 INSERT 操作,等了50秒超時(shí)事務(wù)回滾了。同樣的代碼時(shí)好時(shí)壞,那就一定和觸發(fā)條件有關(guān)系了。
對應(yīng)正在執(zhí)行的是一個(gè) INSERT 的操作
2022-xx-xx 15:x:x.380 [elapse:50674] [sql:INSERT INTO xxx_table ....]
按照前面的固有思路,即將執(zhí)行 INSERT 的一行數(shù)據(jù),理論上和別人沒什么的沖突,為啥會拿不到鎖呢?
在代碼邏輯里也不能明確定位問題,只能求助 DBA 幫忙 dump 事務(wù)日志相關(guān)信息。
但內(nèi)容里也沒有死鎖信息,事務(wù)日志里也僅有 Transaction 在等待鎖的信息,像這個(gè)樣子,大概看了一眼,不像死鎖日志里有一個(gè) Hold Locks 信息,這種普通的情況,具體鎖在誰手里,還是兩眼一抹眼。
------------------
---TRANSACTION 13934594, ACTIVE 41 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1
MySQL thread id 6695850, OS thread handle 0x7ef74b2c0700, query id 123 xxx abc update
INSERT INTO xxx_table(col,col1,...)
------- TRX HAS BEEN WAITING 41 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1057 page no 3724 n bits 312 index `xxx_id_idx` of table `test`.`xxx_table` trx id 13934594 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 241 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
0: len 8; hex 800000008d8faf7d; asc };;
1: len 5; hex ....123; asc 12_34;;
2: len 17; hex 111111111111113732333338; asc 111111111111111272338;;
3: len 1; hex 80; asc ;;
4: len 8; hex 8000000000012adf; asc * ;;
------------------
沒有其它辦法,只好回過來仔細(xì)看事務(wù)日志。仔細(xì)看這里的 WAITING xx for this lock 下面,會提到這個(gè)等待鎖的類型:
RECORD LOCKS index `xxx_id_idx` of table `test`.`xxx_table` lock_mode X locks gap before rec insert intention waiting
期中提到了
lock_mode X locks gap before rec insert intention waiting
是 GAP 鎖,那這個(gè)間隙有多大?
再向后看,提到了索引。是因?yàn)楸砝锏倪@個(gè)索引,而后面的 Recod Lock 剛好就是這個(gè)索引對應(yīng)的各個(gè)字段,那對應(yīng)到索引的定義,發(fā)現(xiàn)里面有一個(gè)字段剛好是某個(gè)業(yè)務(wù)屬性的 id。
之前對事務(wù)日志不熟悉,這算一個(gè)比較重要的發(fā)現(xiàn),根據(jù)這個(gè) id 繼續(xù)去查庫時(shí),發(fā)現(xiàn)這條記錄是在前一刻剛剛寫到庫里。
一個(gè)剛寫到庫里的字段,和新的要 INSERT 的數(shù)據(jù)有什么關(guān)聯(lián)呢?
此時(shí)仔細(xì)回想了一下業(yè)務(wù)邏輯,想起我們有一個(gè)異步的操作,會在數(shù)據(jù)執(zhí)行之后,在某些條件下,去做更新這條記錄的操作。因?yàn)檫@個(gè)更新操作涉及到更新多個(gè)表,還加了個(gè)事務(wù)。只是因?yàn)椴皇怯脩粽埱螅辉旁谝黄鸾y(tǒng)一看過。
而我們前面的 INSERT 這個(gè),也是在一個(gè)事務(wù)里,先執(zhí)行 INSERT 再執(zhí)行一個(gè) UPDATE 的操作,可以這樣理解:
會話 1先執(zhí)行:
BEGIN;
1. UPDATE xxx_table SET update_time='xxx' WHERE id = '123' ;
3.再執(zhí)行一個(gè)其他操作
會話2 執(zhí)行:
BEGIN;
2.INSERT INTO `xxx_table` (col1,col2)...
4.再執(zhí)行一個(gè)操作
此時(shí),我們看到兩個(gè)因?yàn)殒i的交叉使用,導(dǎo)致誰都沒法完成,最終直到超時(shí)。
為什么?
那為什么一個(gè) INSERT 會受到前面不相關(guān)的 UPDATE 操作的影響呢?
這就不得不提 MySQL 里的間隙鎖 (GAP Lock)。業(yè)務(wù)里的 id,就是在索引 里使用到的那個(gè),是通過某個(gè)服務(wù)生成的。而已經(jīng)寫入到庫里的那條,id 要比我們新 INSERT 的這條,要大。GAP Lock 剛好鎖定的是新寫的 id 到成功寫入的這條 ID。而這個(gè)寫入成功的 ID,在前面正在被 UPDATE,所以兩個(gè)操作就沖突了。
在線下模擬的話,可以通過 MySQL 自帶的幾個(gè)表,來查看鎖的占用信息,可以清晰的看出來,兩個(gè)操作的 lock data
是同一個(gè)數(shù)據(jù),不沖突才怪呢。
mysql> SELECT * FROM `information_schema`.INNODB_LOCKS\G;
*************************** 1. row ***************************
lock_id: 225753412:5845:5:253
lock_trx_id: 225753412
lock_mode: X,GAP
lock_type: RECORD
lock_table: `db`.`xxx_table`
lock_index: xxx_id_idx
lock_space: 5845
lock_page: 5
lock_rec: 253
lock_data: 3094360230, 'abc-01', '111623639', 0, 255
*************************** 2. row ***************************
lock_id: 225751488:5845:5:253
lock_trx_id: 225751488
lock_mode: X
lock_type: RECORD
lock_table: `db`.`xxx_table`
lock_index: xxx_id_idx
lock_space: 5845
lock_page: 5
lock_rec: 253
lock_data: 3094360230, 'abc-01', '111623639', 0, 255
2 rows in set (0.04 sec)
mysql> select * from `information_schema`.INNODB_LOCK_WAITS\G;
*************************** 1. row ***************************
requesting_trx_id: 225753412 // 申請資源的事務(wù) ID
requested_lock_id: 225753412:5845:5:253
blocking_trx_id: 225751488 // 阻塞的事務(wù) ID
blocking_lock_id: 225751488:5845:5:253
1 row in set (0.04 sec)
mysql> select * from `information_schema`.INNODB_TRX\G;
*************************** 1. row ***************************
trx_id: 225751488
trx_state: LOCK WAIT
trx_started: 2022-0xxx
trx_requested_lock_id: 225851026:5874:4:1
trx_wait_started: 2022-05-2xxx
trx_weight: 3
trx_mysql_thread_id: 1875826
trx_query: insert into xxx_table values(...)
trx_operation_state: inserting
trx_tables_in_use: 1
trx_tables_locked: 1
trx_lock_structs: 2
trx_lock_memory_bytes: 360
trx_rows_locked: 1
trx_rows_modified: 1
trx_concurrency_tickets: 0
trx_isolation_level: REPEATABLE READ
trx_unique_checks: 1
trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
trx_adaptive_hash_latched: 0
trx_adaptive_hash_timeout: 10000
trx_is_read_only: 0
trx_autocommit_non_locking: 0
*************************** 2. row ***************************
trx_id: 225753412
trx_state: RUNNING
trx_started: 2022-0xxx
trx_requested_lock_id: NULL
trx_wait_started: NULL
trx_weight: 4
trx_mysql_thread_id: 1875454
trx_query: NULL
trx_operation_state: NULL
trx_tables_in_use: 0
trx_tables_locked: 0
trx_lock_structs: 3
trx_lock_memory_bytes: 360
trx_rows_locked: 3
trx_rows_modified: 1
trx_concurrency_tickets: 0
trx_isolation_level: REPEATABLE READ
trx_unique_checks: 1
trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
trx_adaptive_hash_latched: 0
trx_adaptive_hash_timeout: 10000
trx_is_read_only: 0
trx_autocommit_non_locking: 0
2 rows in set (0.03 sec)
有些時(shí)候,我們主觀上認(rèn)為的一個(gè) INSERT ,在新寫入數(shù)據(jù),理論上除了表鎖和別人沒啥沖突,畢竟還沒寫,也沒人能更新它。不過這些細(xì)節(jié)上,間隙鎖,Next Key Lock 等等,還是會影響到具體的執(zhí)行。如果你也遇到類似的情況,有權(quán)限的時(shí)候,可以通過上面 MySQL 'information_schema'.INNODB_自帶的三個(gè)表,發(fā)現(xiàn)鎖的沖突信息。如果沒有權(quán)限,可以先想辦法拿到事務(wù)日志,再進(jìn)一步分析。
事務(wù)日志,可以通過 SHOW ENGINE INNODB STATUS 拿到,給迷茫的分析加一點(diǎn)思路。
網(wǎng)站標(biāo)題:我一個(gè)INSERT 還能被你 UPDATE 給卡???
當(dāng)前網(wǎng)址:http://www.dlmjj.cn/article/cdgieog.html


咨詢
建站咨詢
