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

創(chuàng)新互聯(lián)是一家從事企業(yè)網(wǎng)站建設、成都網(wǎng)站建設、成都做網(wǎng)站、行業(yè)門戶網(wǎng)站建設、網(wǎng)頁設計制作的專業(yè)網(wǎng)站設計公司,擁有經驗豐富的網(wǎng)站建設工程師和網(wǎng)頁設計人員,具備各種規(guī)模與類型網(wǎng)站建設的實力,在網(wǎng)站建設領域樹立了自己獨特的設計風格。自公司成立以來曾獨立設計制作的站點近千家。
隨著鎖定范圍的不同,鎖與鎖之間的互相影響也差異很大,這一點很好理解。比如一個操作加了表鎖之后,另一個想加行鎖就得等待;而一個行鎖一般并不會影響鎖另一行的行鎖。
除了書本上和八股文,你有沒有遇到過這些鎖相關的問題呢?
我先來說一個最近遇到的。
現(xiàn)象
某天,項目出現(xiàn)幾條監(jiān)控報警,都是在寫庫的時候獲取鎖超時導致。業(yè)務會在某種特定的場景下,出現(xiàn)如下的 MySQL 獲取鎖超時,事務回滾的異常。
org.springframework.dao.CannotAcquireLockException:
### Error updating database.
Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: xxx
Lock wait timeout exceeded; try restarting transaction
看了下錯誤對應的日志,發(fā)現(xiàn)當時 MySQL 要執(zhí)行一條 INSERT 操作,等了50秒超時事務回滾了。同樣的代碼時好時壞,那就一定和觸發(fā)條件有關系了。
對應正在執(zhí)行的是一個 INSERT 的操作
2022-xx-xx 15:x:x.380 [elapse:50674] [sql:INSERT INTO xxx_table ....]
按照前面的固有思路,即將執(zhí)行 INSERT 的一行數(shù)據(jù),理論上和別人沒什么的沖突,為啥會拿不到鎖呢?
在代碼邏輯里也不能明確定位問題,只能求助 DBA 幫忙 dump 事務日志相關信息。
但內容里也沒有死鎖信息,事務日志里也僅有 Transaction 在等待鎖的信息,像這個樣子,大概看了一眼,不像死鎖日志里有一個 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 * ;;
------------------
沒有其它辦法,只好回過來仔細看事務日志。仔細看這里的 WAITING xx for this lock 下面,會提到這個等待鎖的類型:
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 鎖,那這個間隙有多大?
再向后看,提到了索引。是因為表里的這個索引,而后面的 Recod Lock 剛好就是這個索引對應的各個字段,那對應到索引的定義,發(fā)現(xiàn)里面有一個字段剛好是某個業(yè)務屬性的 id。
之前對事務日志不熟悉,這算一個比較重要的發(fā)現(xiàn),根據(jù)這個 id 繼續(xù)去查庫時,發(fā)現(xiàn)這條記錄是在前一刻剛剛寫到庫里。
一個剛寫到庫里的字段,和新的要 INSERT 的數(shù)據(jù)有什么關聯(lián)呢?
此時仔細回想了一下業(yè)務邏輯,想起我們有一個異步的操作,會在數(shù)據(jù)執(zhí)行之后,在某些條件下,去做更新這條記錄的操作。因為這個更新操作涉及到更新多個表,還加了個事務。只是因為不是用戶請求,不曾放在一起統(tǒng)一看過。
而我們前面的 INSERT 這個,也是在一個事務里,先執(zhí)行 INSERT 再執(zhí)行一個 UPDATE 的操作,可以這樣理解:
會話 1先執(zhí)行:
BEGIN;
1. UPDATE xxx_table SET update_time='xxx' WHERE id = '123' ;
3.再執(zhí)行一個其他操作
會話2 執(zhí)行:
BEGIN;
2.INSERT INTO `xxx_table` (col1,col2)...
4.再執(zhí)行一個操作
此時,我們看到兩個因為鎖的交叉使用,導致誰都沒法完成,最終直到超時。
為什么?
那為什么一個 INSERT 會受到前面不相關的 UPDATE 操作的影響呢?
這就不得不提 MySQL 里的間隙鎖 (GAP Lock)。業(yè)務里的 id,就是在索引 里使用到的那個,是通過某個服務生成的。而已經寫入到庫里的那條,id 要比我們新 INSERT 的這條,要大。GAP Lock 剛好鎖定的是新寫的 id 到成功寫入的這條 ID。而這個寫入成功的 ID,在前面正在被 UPDATE,所以兩個操作就沖突了。
在線下模擬的話,可以通過 MySQL 自帶的幾個表,來查看鎖的占用信息,可以清晰的看出來,兩個操作的 lock data
是同一個數(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 // 申請資源的事務 ID
requested_lock_id: 225753412:5845:5:253
blocking_trx_id: 225751488 // 阻塞的事務 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)
有些時候,我們主觀上認為的一個 INSERT ,在新寫入數(shù)據(jù),理論上除了表鎖和別人沒啥沖突,畢竟還沒寫,也沒人能更新它。不過這些細節(jié)上,間隙鎖,Next Key Lock 等等,還是會影響到具體的執(zhí)行。如果你也遇到類似的情況,有權限的時候,可以通過上面 MySQL 'information_schema'.INNODB_自帶的三個表,發(fā)現(xiàn)鎖的沖突信息。如果沒有權限,可以先想辦法拿到事務日志,再進一步分析。
事務日志,可以通過 SHOW ENGINE INNODB STATUS 拿到,給迷茫的分析加一點思路。
分享名稱:我一個INSERT 還能被你 UPDATE 給卡???
標題網(wǎng)址:http://www.5511xx.com/article/cdgieog.html


咨詢
建站咨詢
