<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          我一個(gè)INSERT 還能被你 UPDATE 給卡???

          共 5968字,需瀏覽 12分鐘

           ·

          2022-05-30 13:33


          許多語(yǔ)言和工具都通過(guò)鎖,來(lái)保證并發(fā)場(chǎng)景下數(shù)據(jù)和邏輯的正確性,MySQL 也不例外。除了行鎖、表鎖這種范圍粒度外,還有這種針對(duì)讀和寫(xiě)的 S鎖共享鎖 和 X鎖獨(dú)占鎖。


          隨著鎖定范圍的不同,鎖與鎖之間的互相影響也差異很大,這一點(diǎn)很好理解。比如一個(gè)操作加了表鎖之后,另一個(gè)想加行鎖就得等待;而一個(gè)行鎖一般并不會(huì)影響鎖另一行的行鎖。


          除了書(shū)本上和八股文,你有沒(méi)有遇到過(guò)這些鎖相關(guān)的問(wèn)題呢?

          我先來(lái)說(shuō)一個(gè)最近遇到的。


          現(xiàn)象

          某天,項(xiàng)目出現(xiàn)幾條監(jiān)控報(bào)警,都是在寫(xiě)庫(kù)的時(shí)候獲取鎖超時(shí)導(dǎo)致。業(yè)務(wù)會(huì)在某種特定的場(chǎng)景下,出現(xiàn)如下的 MySQL 獲取鎖超時(shí),事務(wù)回滾的異常。


          org.springframework.dao.CannotAcquireLockException:### Error updating database.  Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: xxxLock?wait?timeout?exceeded;?try?restarting?transaction


          看了下錯(cuò)誤對(duì)應(yīng)的日志,發(fā)現(xiàn)當(dāng)時(shí) MySQL 要執(zhí)行一條 INSERT 操作,等了50秒超時(shí)事務(wù)回滾了。同樣的代碼時(shí)好時(shí)壞,那就一定和觸發(fā)條件有關(guān)系了。


          對(duì)應(yīng)正在執(zhí)行的是一個(gè) INSERT 的操作2022-xx-xx?15:x:x.380?[elapse:50674]?[sql:INSERT?INTO?xxx_table?....]


          按照前面的固有思路,即將執(zhí)行 INSERT 的一行數(shù)據(jù),理論上和別人沒(méi)什么的沖突,為啥會(huì)拿不到鎖呢?


          在代碼邏輯里也不能明確定位問(wèn)題,只能求助 DBA 幫忙? dump 事務(wù)日志相關(guān)信息。


          但內(nèi)容里也沒(méi)有死鎖信息,事務(wù)日志里也僅有 Transaction 在等待鎖的信息,像這個(gè)樣子,大概看了一眼,不像死鎖日志里有一個(gè) Hold Locks 信息,這種普通的情況,具體鎖在誰(shuí)手里,還是兩眼一抹眼。


          ---------------------TRANSACTION 13934594, ACTIVE 41 sec insertingmysql tables in use 1, locked 1LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1MySQL thread id 6695850, OS thread handle 0x7ef74b2c0700, query id 123 xxx abc updateINSERT?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 waitingRecord 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???????*?;;------------------


          沒(méi)有其它辦法,只好回過(guò)來(lái)仔細(xì)看事務(wù)日志。仔細(xì)看這里的 WAITING xx FOR THIS LOCK 下面,會(huì)提到這個(gè)等待鎖的類(lèi)型

          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è)索引對(duì)應(yīng)的各個(gè)字段,那對(duì)應(yīng)到索引的定義,發(fā)現(xiàn)里面有一個(gè)字段剛好是某個(gè)業(yè)務(wù)屬性的 id。


          之前對(duì)事務(wù)日志不熟悉,這算一個(gè)比較重要的發(fā)現(xiàn),根據(jù)這個(gè) id 繼續(xù)去查庫(kù)時(shí),發(fā)現(xiàn)這條記錄是在前一刻剛剛寫(xiě)到庫(kù)里。


          一個(gè)剛寫(xiě)到庫(kù)里的字段,和新的要 INSERT 的數(shù)據(jù)有什么關(guān)聯(lián)呢?

          此時(shí)仔細(xì)回想了一下業(yè)務(wù)邏輯,想起我們有一個(gè)異步的操作,會(huì)在數(shù)據(jù)執(zhí)行之后,在某些條件下,去做更新這條記錄的操作。因?yàn)檫@個(gè)更新操作涉及到更新多個(gè)表,還加了個(gè)事務(wù)。只是因?yàn)椴皇怯脩粽?qǐng)求,不曾放在一起統(tǒng)一看過(guò)。


          而我們前面的 INSERT 這個(gè),也是在一個(gè)事務(wù)里,先執(zhí)行 INSERT 再執(zhí)行一個(gè) UPDATE 的操作,可以這樣理解:


          會(huì)話 1先執(zhí)行:

          BEGIN;

          1.?UPDATE xxx_table? SET update_time='xxx'? ??WHERE id = '123' ;

          3.再執(zhí)行一個(gè)其他操作


          會(huì)話2 執(zhí)行:

          BEGIN;

          2.INSERT INTO `xxx_table` (col1,col2)...

          4.再執(zhí)行一個(gè)操作


          此時(shí),我們看到兩個(gè)因?yàn)殒i的交叉使用,導(dǎo)致誰(shuí)都沒(méi)法完成,最終直到超時(shí)。


          為什么?

          那為什么一個(gè) INSERT 會(huì)受到前面不相關(guān)的 UPDATE 操作的影響呢?


          這就不得不提 MySQL 里的間隙鎖?(GAP Lock)。業(yè)務(wù)里的 id,就是在索引 里使用到的那個(gè),是通過(guò)某個(gè)服務(wù)生成的。而已經(jīng)寫(xiě)入到庫(kù)里的那條,id 要比我們新 INSERT 的這條,要大。GAP Lock 剛好鎖定的是新寫(xiě)的 id 到成功寫(xiě)入的這條 ID。而這個(gè)寫(xiě)入成功的 ID,在前面正在被 UPDATE,所以?xún)蓚€(gè)操作就沖突了。


          在線下模擬的話,可以通過(guò) MySQL 自帶的幾個(gè)表,來(lái)查看鎖的占用信息,可以清晰的看出來(lái),兩個(gè)操作的 lock data 是同一個(gè)數(shù)據(jù),不沖突才怪呢。


          mysql> SELECT * FROM `information_schema`.INNODB_LOCKS\G;*************************** 1. row ***************************    lock_id: 225753412:5845:5:253lock_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:253lock_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, 2552 rows in set (0.04 sec)


          mysql> select * from `information_schema`.INNODB_LOCK_WAITS\G;*************************** 1. row ***************************requesting_trx_id: 225753412          //  申請(qǐng)資源的事務(wù) IDrequested_lock_id: 225753412:5845:5:253  blocking_trx_id: 225751488             // 阻塞的事務(wù) ID blocking_lock_id: 225751488:5845:5:2531 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: 1trx_last_foreign_key_error: NULL trx_adaptive_hash_latched: 0 trx_adaptive_hash_timeout: 10000          trx_is_read_only: 0trx_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: 1trx_last_foreign_key_error: NULL trx_adaptive_hash_latched: 0 trx_adaptive_hash_timeout: 10000          trx_is_read_only: 0trx_autocommit_non_locking: 02 rows in set (0.03 sec)


          有些時(shí)候,我們主觀上認(rèn)為的一個(gè) INSERT ,在新寫(xiě)入數(shù)據(jù),理論上除了表鎖和別人沒(méi)啥沖突,畢竟還沒(méi)寫(xiě),也沒(méi)人能更新它。不過(guò)這些細(xì)節(jié)上,間隙鎖,Next Key Lock 等等,還是會(huì)影響到具體的執(zhí)行。如果你也遇到類(lèi)似的情況,有權(quán)限的時(shí)候,可以通過(guò)上面 MySQL `information_schema`.INNODB_自帶的

          三個(gè)表,發(fā)現(xiàn)鎖的沖突信息。如果沒(méi)有權(quán)限,可以先想辦法拿到事務(wù)日志,再進(jìn)一步分析。

          事務(wù)日志,可以通過(guò)?SHOW ENGINE INNODB STATUS 拿到,給迷茫的分析加一點(diǎn)思路。


          你有遇過(guò)到什么排查問(wèn)題中的「峰回路轉(zhuǎn)」嗎?歡迎分享。


          相關(guān)閱讀

          免安裝,還原生產(chǎn)環(huán)境,運(yùn)行中切換版本,這不是我認(rèn)識(shí)的MySQL

          五分鐘爆改,把你的JSON/CSV文件打造成MySQL數(shù)據(jù)庫(kù)

          數(shù)據(jù)庫(kù)是咋工作的?(一)

          怎樣查看運(yùn)行中的 Spring 應(yīng)用配置?

          瀏覽 33
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  久久大鸡吧 | 亚洲性受 | 国产色婷婷拳交视频 | 精品无码一区二区三区天香 | av有码第一页 |