精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

MySQL并發update導致鎖等待介紹

冬至子 ? 來源:丹柿小院 ? 作者:鳥山明 ? 2023-05-19 10:54 ? 次閱讀

引言

生產環境中經常會遇到鎖等待與死鎖相關的問題,這類問題通常比較緊急,而且由于鎖相關影響因素較多,因此分析難度較大。

本文從最簡單的一類鎖等待開始,即并發 update 導致鎖等待。

介紹

如果相同的 update 同時執行會發生什么呢?

實際上會發生鎖等待,生產環境中就遇到過這種案例,并發 update 導致鎖等待。

死鎖建立在鎖等待的基礎上,因此需要先理解鎖等待的機制與分析思路。本文通過一個最簡單的并發 update 介紹鎖等待的分析方法。

模擬

首先,聲明事務隔離級別為 RR(REPEATABLE-READ)。

流程

兩個 session 分別在開啟事務的前提下執行相同的 update 語句導致鎖等待。

其中超時時間由系統參數 innodb_lock_wait_timeout 控制,默認值 50s,當前值 120s。

mysql> select @@innodb_lock_wait_timeout;
+----------------------------+
| @@innodb_lock_wait_timeout |
+----------------------------+
|                        120 |
+----------------------------+
1 row in set (0.00 sec)

根據官方文檔,innodb_lock_wait_timeout 參數控制 InnoDB 存儲引擎中事務的行鎖等待時間,超時回滾。

innodb_lock_wait_timeout

The length of time in seconds an InnoDB transaction waits for a row lock before giving up.

MySQL 5.7 中查看事務加鎖的情況有兩種方式:

  • 使用 information_schema 數據庫中的表獲取鎖信息
  • 使用 SHOW ENGINE INNODB STATUS 獲取鎖信息。

下面分別使用這兩種方式分析當前事務加鎖的情況。

innodb_trx

information_schema.innodb_trx 表中存儲了 InnoDB 存儲引擎當前正在執行的事務信息。

其中:

  • TRX_TABLES_LOCKED 字段表示事務當前執行 SQL 持有行鎖涉及到的表的數量,注意不包括表鎖,因此盡管部分行被鎖定,但通常不影響其他事務的讀寫操作;

TRX_TABLES_LOCKED

The number of InnoDB tables that the current SQL statement has row locks on. (Because these are row locks, not table locks, the tables can usually still be read from and written to by multiple transactions, despite some rows being locked.)

  • TRX_ROWS_LOCKED 字段表示被事務鎖定的行數,其中可能包括被標記為刪除但實際上未物理刪除的數據行。

TRX_ROWS_LOCKED

The approximate number or rows locked by this transaction. The value might include delete-marked rows that are physically present but not visible to the transaction.

結果表明當前有兩個未提交事務,不同點是其中一個執行中,一個鎖等待,相同點是都在內存中創建了兩個鎖結構,而且其中一個是行鎖。

mysql> select * from information_schema.innodb_trx\\G
*************************** 1. row ***************************
                    trx_id: 11309021
                 trx_state: LOCK WAIT
               trx_started: 2022-11-22 17:40:16
     trx_requested_lock_id: 11309021:190:3:2
          trx_wait_started: 2022-11-22 17:42:25
                trx_weight: 2
       trx_mysql_thread_id: 1135
                 trx_query: update t2 set name='d' where id=1
       trx_operation_state: starting index read
         trx_tables_in_use: 1
         trx_tables_locked: 1  # 1個表上有行鎖
          trx_lock_structs: 2  # 內存中2個鎖結構
     trx_lock_memory_bytes: 1136
           trx_rows_locked: 1  # 1行數據被鎖定
         trx_rows_modified: 0
   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: 0
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
*************************** 2. row ***************************
                    trx_id: 11309020
                 trx_state: RUNNING
               trx_started: 2022-11-22 17:40:09
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 3
       trx_mysql_thread_id: 1134
                 trx_query: NULL
       trx_operation_state: NULL
         trx_tables_in_use: 0
         trx_tables_locked: 1  # 1個表上有行鎖
          trx_lock_structs: 2  # 內存中2個鎖結構
     trx_lock_memory_bytes: 1136
           trx_rows_locked: 1  # 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: 0
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
2 rows in set (0.00 sec)

從中可以看到與鎖相關的事務,但是無法看到鎖的具體類型。

innodb_locks

information_schema.innodb_locks 表中主要包括以下兩方面的鎖信息:

  • 如果一個事務想要獲取某個鎖但未獲取到,則記錄該鎖信息,即等鎖事務;
  • 如果一個事務獲取到了某個鎖,但是這個鎖阻塞了其他事務,則記錄該鎖信息,即持鎖事務。

The INNODB_LOCKS table provides information about each lock that an InnoDB transaction has requested but not yet acquired, and each lock that a transaction holds that is blocking another transaction.

注意只有當事務因為獲取不到鎖而被阻塞即發生鎖等待時 innodb_locks 表中才會有記錄,因此當只有一個事務時,無法查看該事務所加的鎖信息。

如下所示,鎖超時之后查詢 innodb_locks 表,結果為空。

mysql> select * from information_schema.innodb_locks\\G
Empty set, 1 warning (0.00 sec)

如下所示,鎖超時之前查詢 innodb_locks 表,結果表明所有事務共請求了兩次 t2 表的主鍵索引值為 1 的記錄上的 X 型行鎖。

mysql> select * from information_schema.innodb_locks \\G
*************************** 1. row ***************************
    lock_id: 11309021:190:3:2
lock_trx_id: 11309021
  lock_mode: X  # 排它鎖
  lock_type: RECORD  # 行鎖
 lock_table: `test_zk`.`t2`  # 表名
 lock_index: PRIMARY  # 主鍵索引
 lock_space: 190
  lock_page: 3
   lock_rec: 2
  lock_data: 1  # 主鍵值為1
*************************** 2. row ***************************
    lock_id: 11309020:190:3:2
lock_trx_id: 11309020
  lock_mode: X  # 排它鎖
  lock_type: RECORD  # 行鎖
 lock_table: `test_zk`.`t2`  # 表名
 lock_index: PRIMARY  # 主鍵索引
 lock_space: 190
  lock_page: 3
   lock_rec: 2
  lock_data: 1  # 主鍵值為1
2 rows in set, 1 warning (0.00 sec)

從中可以看到具體請求的鎖的類型,但是無法區分等鎖事務與持鎖事務。

innodb_lock_waits

information_schema.innodb_lock_waits 表中記錄每個阻塞的事務是因為獲取不到哪個事務持有的鎖而阻塞。

結果表明 11309020 事務阻塞了 11309021 事務。

mysql> select * from information_schema.innodb_lock_waits;
+-------------------+-------------------+-----------------+------------------+
| requesting_trx_id | requested_lock_id | blocking_trx_id | blocking_lock_id |
+-------------------+-------------------+-----------------+------------------+
| 11309021          | 11309021:190:3:2  | 11309020        | 11309020:190:3:2 |
+-------------------+-------------------+-----------------+------------------+
1 row in set, 1 warning (0.00 sec)

從中可以看到事務之間鎖的依賴關系,但是無法查看到持鎖 SQL,因此通常需要將該表與其他表做關聯查詢。

關聯查詢

如下所示,可以在發生鎖等待的現場關聯查詢 information_schema 數據庫中的多張表表分析持鎖與等鎖的事務與 SQL。

mysql> SELECT r.trx_id waiting_trx_id,  
    ->        r.trx_mysql_thread_id waiting_thread,
    ->        r.trx_query waiting_query,
    ->        b.trx_id blocking_trx_id, 
    ->        b.trx_mysql_thread_id blocking_thread,
    ->        b.trx_query blocking_query
    ->    FROM       information_schema.innodb_lock_waits w
    ->    INNER JOIN information_schema.innodb_trx b  ON  
    ->     b.trx_id = w.blocking_trx_id
    ->   INNER JOIN information_schema.innodb_trx r  ON  
    ->     r.trx_id = w.requesting_trx_id;
*************************** 1. row ***************************
 waiting_trx_id: 11309021
 waiting_thread: 1135
  waiting_query: update t2 set name='d' where id=1
blocking_trx_id: 11309020
blocking_thread: 1134
 blocking_query: NULL
1 row in set, 1 warning (0.00 sec)

注意其中從 information_schema.innodb_trx 表中查詢到的 blocking_query 即持鎖的 SQL 為空。

實際上,可以從 performance_schema.events_statements_current 表中查詢到持鎖 SQL。

mysql> select 
    ->     wt.thread_id waiting_thread_id,
    ->     wt.processlist_id waiting_processlist_id,
    ->     wt.processlist_time waiting_time,
    ->     wt.processlist_info waiting_query,
    ->     bt.thread_id blocking_thread_id,
    ->     bt.processlist_id blocking_processlist_id,
    ->     bt.processlist_time blocking_time,
    ->     c.sql_text blocking_query,
    ->     concat('kill ',bt.processlist_id, ';') sql_kill_blocking_connection
    -> from information_schema.innodb_lock_waits l join information_schema.innodb_trx b
    ->     on b.trx_id = l.blocking_trx_id
    -> join information_schema.innodb_trx w
    ->     on w.trx_id = l.requesting_trx_id
    -> join performance_schema.threads wt
    ->     on w.trx_mysql_thread_id=wt.processlist_id
    -> join performance_schema.threads bt
    ->     on b.trx_mysql_thread_id=bt.processlist_id
    -> join performance_schema.events_statements_current c
    ->     on bt.thread_id=c.thread_id \\G
*************************** 1. row ***************************
           waiting_thread_id: 1178
      waiting_processlist_id: 1135
                waiting_time: 61
               waiting_query: update t2 set name='d' where id=1
          blocking_thread_id: 1177
     blocking_processlist_id: 1134
               blocking_time: 76
              blocking_query: update t2 set name='d' where id=1
sql_kill_blocking_connection: kill 1134;
1 row in set, 1 warning (0.00 sec)

INNODB STATUS

SHOW ENGINE INNODB STATUS 命令用于查詢 InnoDB 存儲引擎標準監控的狀態信息。

SHOW ENGINE INNODB STATUS displays extensive information from the standard InnoDB Monitor about the state of the InnoDB storage engine.

其中 TRANSACTIONS 部分的信息可用于分析鎖等待與死鎖。

TRANSACTIONS

If this section reports lock waits, your applications might have lock contention. The output can also help to trace the reasons for transaction deadlocks.

結果如下所示,TRANSACTIONS 部分包括兩個未提交事務。

mysql> show engine innodb status \\G
*************************** 1. row ***************************
  Type: InnoDB
  Name: 
Status: 
=====================================
2022-11-22 17:42:50 0x7ff4df900700 INNODB MONITOR OUTPUT
=====================================
Per second averages calculated from the last 50 seconds
...
------------
TRANSACTIONS
------------
# 下一個待分配的事務id信息
Trx id counter 11309022
# 清除舊MVCC行時使用的事務ID,該事務與當前事務之間的老版本數據未被清除
Purge done for trx's n:o < 11309020 undo n:o < 0 state: running but idle
# 每個回滾段都有一個History鏈表,這些鏈表的總長度等于64
History list length 64
# 各個事務的具體信息
LIST OF TRANSACTIONS FOR EACH SESSION:
# not started 空閑事務,表示事務已經提交并且沒有再發起影響事務的語句
---TRANSACTION 422165848318464, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 422165848316640, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
# 事務ID等于11309021的事務,處于活躍狀態154秒,正在使用索引讀取數據行
---TRANSACTION 11309021, ACTIVE 154 sec starting index read
# 事務11309021正在使用1張表,有1張表有鎖
mysql tables in use 1, locked 1
# 等鎖,鎖鏈表長度為2,占用內存1136字節,其中1把行鎖
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 1135, OS thread handle 140689506727680, query id 13803596 127.0.0.1 admin updating
# 事務運行中SQL語句
update t2 set name='d' where id=1
# 鎖等待發生時在等待的鎖信息,已等待25秒
------- TRX HAS BEEN WAITING 25 SEC FOR THIS LOCK TO BE GRANTED:
# 等鎖,在等待主鍵索引(index PRIMARY)上的行級別X鎖(RECORD LOCK),沒有間隙鎖
RECORD LOCKS space id 190 page no 3 n bits 80 index PRIMARY of table `test_zk`.`t2` trx id 11309021 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
# 內存地址,用于調試
 0: len 4; hex 80000001; asc     ;;  # 聚簇索引的值,80000001 表示主鍵值為1
 1: len 6; hex 000000ac8fdc; asc       ;;  # 事務ID,對應十進制 11309020
 2: len 7; hex 730000002a0b0d; asc s   *  ;;  # unod記錄
 3: len 1; hex 64; asc d;;  # 非主鍵字段的值,'d'

------------------
# 持鎖,事務ID等于11309021的事務對t2表加了表級別的意向排它鎖
TABLE LOCK table `test_zk`.`t2` trx id 11309021 lock mode IX
# 等鎖,在等待主鍵索引(index PRIMARY)上的行級別X鎖(RECORD LOCK),沒有間隙鎖
RECORD LOCKS space id 190 page no 3 n bits 80 index PRIMARY of table `test_zk`.`t2` trx id 11309021 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 000000ac8fdc; asc       ;;
 2: len 7; hex 730000002a0b0d; asc s   *  ;;
 3: len 1; hex 64; asc d;;

# 事務ID等于11309020的事務,處于活躍狀態161秒
---TRANSACTION 11309020, ACTIVE 161 sec
# 該事務有2個鎖結構,其中1個行鎖
2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 1134, OS thread handle 140689373869824, query id 13803593 127.0.0.1 admin
# 持鎖,事務ID等于11309020的事務對t2表加了表級別的意向排它鎖,IX鎖之間兼容
TABLE LOCK table `test_zk`.`t2` trx id 11309020 lock mode IX
# 持鎖,主鍵索引(index PRIMARY)上的行級別X鎖(RECORD LOCK),沒有間隙鎖
RECORD LOCKS space id 190 page no 3 n bits 80 index PRIMARY of table `test_zk`.`t2` trx id 11309020 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;  # 80000001 表示主鍵值為1
 1: len 6; hex 000000ac8fdc; asc       ;;
 2: len 7; hex 730000002a0b0d; asc s   *  ;;
 3: len 1; hex 64; asc d;;

...
----------------------------
END OF INNODB MONITOR OUTPUT
============================

從中可以看到事務持鎖與等鎖的詳細信息,但是無法看到持鎖的 SQL。

由于信息不全,因此 SHOW ENGINE INNODB STATUS 更適合分析死鎖,因為死鎖已經沒有了現場,而鎖等待通常現場還在,可以直接查看 information_schema 數據庫中的表。

主要信息如下所示。

  • 11309021 事務持有 t2 表的表級別意向排它鎖,等待主鍵索引上的行級別 X 鎖(RECORD LOCK),沒有間隙鎖;
---TRANSACTION 11309021, ACTIVE 154 sec starting index read
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
update t2 set name='d' where id=1
TABLE LOCK table `test_zk`.`t2` trx id 11309021 lock mode IX
RECORD LOCKS space id 190 page no 3 n bits 80 index PRIMARY of table `test_zk`.`t2` trx id 11309021 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
  • 11309020 事務分別持有 t2 表的表級別意向排它鎖與主鍵索引上的行級別 X 鎖(RECORD LOCK),沒有間隙鎖。
---TRANSACTION 11309020, ACTIVE 161 sec
2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
TABLE LOCK table `test_zk`.`t2` trx id 11309020 lock mode IX
RECORD LOCKS space id 190 page no 3 n bits 80 index PRIMARY of table `test_zk`.`t2` trx id 11309020 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0

因此,鎖等待分析的結論如下所示:

  • update 操作需要獲取兩把鎖,包括表級別的意向排它鎖與行級別 X 鎖(RECORD LOCK);
  • 并發 update 時由于意向鎖之間兼容,而行級 X 鎖之間沖突,導致發生鎖等待。

原理

首先為什么需要鎖?

鎖本質上是一種并發控制手段,用于解決事務在并發執行時可能引發的一致性問題。

并發事務訪問相同數據基本上可以分為以下三種情況:

  • 讀-讀,相互不影響,因此允許;
  • 寫-寫,會導致臟寫,因此不允許,通過給記錄加鎖實現;
  • 讀-寫或寫-讀,會導致臟讀、不可重復讀、幻讀。解決方案主要分兩種:
    • MVCC 多版本并發控制,保存符合條件的記錄的多個版本,寫操作針對最新版本,讀操作針對歷史版本。因此讀-寫不沖突;
    • 讀寫操作均加鎖,每次都需要讀取最新版本數據,讀寫操作均采用加鎖方式,因此讀-寫沖突。

而 InnoDB 存儲引擎支持事務與行鎖,并實現了基于 MVCC 的事務并發處理機制。

鎖的類型

如下所示,根據不同的維度,可以將鎖分為不同的類型。

圖片

其中:

  • 根據加鎖機制,實際上就是鎖的實現方式,可以將鎖分為以下兩類:
    • 樂觀鎖,先加鎖后訪問,傳統的關系型數據庫使用這種鎖機制;
    • 悲觀鎖,先訪問后加鎖,常見實現如 CAS、版本號控制。
  • 根據兼容性,可以將鎖分為以下兩類:
    • 共享鎖,Shared-Lock,S 鎖,讀鎖;
    • 排它鎖,Exclusive-Lock,X 鎖,寫鎖。
  • 根據鎖的粒度,可以將鎖分為以下三類:
    • 表鎖,Table-Lock,MyISAM 存儲引擎僅支持表鎖;
    • 頁鎖,Page-Lock,使用相對較少;
    • 行鎖,Row-Lock,InnoDB 存儲引擎也支持行鎖。
  • 根據鎖的模式,可以將鎖分為以下幾種:
    • 行鎖,Record Lock,鎖定一條記錄;
    • 間隙鎖,Gap Lock,鎖定一個范圍,不包括記錄本身;
    • Next-key Lock,鎖定一個范圍的記錄包括記錄本身,Next-key Lock = Record Lock + Gap Lock;
    • 插入意向鎖,Insert Intention Lock,用于行鎖和表鎖共存。

具體各種類型鎖的介紹將在本系列后續文章中逐一介紹。

這里簡單介紹下行鎖,行鎖鎖定的是什么,是索引還是數據?

實際上 InnoDB 行鎖是通過給索引項加鎖實現的 ,如果沒有索引,InnoDB 會通過隱藏的聚簇索引來對記錄加鎖。

因此如果不通過索引條件檢索數據,InnoDB 將對表中所有數據加鎖,實際效果與表鎖一樣。

鎖的結構

對一條記錄加鎖的本質是在內存中創建一個鎖結構與之關聯(隱式鎖除外)。如果有多個鎖,保存在鏈表結構中。

簡化后的鎖結構示意圖如下所示,主要包括 trx 信息與 is_waiting 屬性,分別表示鎖所在的事務信息與當前事務是否在等待,然后將鎖結構與行記錄關聯。

圖片

img

假設事務 T1 改動了這條記錄,就生成了一個鎖結構與該記錄關聯,因此 is_waiting 屬性為 false,表示加鎖成功。

事務 T1 提交之前, 另一個事務 T2 也想改動這條記錄,先去查看有沒有鎖結構與這條記錄關聯,發現有一個鎖結構與之關聯后,也生成了一個鎖結構與該記錄關聯,不過 is_waiting 屬性為 true,表示鎖等待,直到 T1 提交后釋放鎖。

圖片

img

更詳細的 InnoDB 存儲引擎中的事務鎖結構如下所示。

圖片

img

其中:

  • 鎖所在的事務信息:無論表鎖還是行鎖,都是在事務執行過程中給生成的,因此需要加載是哪個事務生成了這個鎖結構;
  • 索引信息:對于行鎖需要記錄加鎖的記錄屬于哪個索引,原因是行鎖是給索引項加鎖;
  • 表鎖/行鎖信息:
    • 對于表鎖,記載這是對哪個表加的鎖,還有其他的一些信息;
    • 對于行鎖,主要記載三個信息,包括 Space ID 記錄所在表空間、Page Number 記錄所在頁號、 n_bit 表示對哪一條記錄加了鎖,對于行鎖,一條記錄對應一個比特位;
  • type_node:32 個比特位,記載三部分信息,包括 lock_mode 鎖的模式、lock_type 鎖的類型和 rec_lock_type 行鎖的具體類型:
    • lock_mode,鎖的模式,占用低 4 位,十進制的 0、1、2、3、4 分別表示表級共享意向鎖 IS、表級排它意向鎖 IX、行級共享鎖 LOCK_S、行級排它鎖 LOCK_X、表級 LOCK_AUTO_INC 自增鎖;
    • lock_type,鎖的類型,占用第 5~8 位,不過現階段只有第 5 位和第 6 位被使用。其中十進制的 16 和 32 分別表示表級鎖與行級鎖;
    • rec_lock_type,行鎖的具體類型,十進制的 0、512、1024、2048 分別表示 LOCK_ORDINARY 即 Next-key Lock、LOCK_GAP 即間隙鎖、LOCK_REC_NOT_GAP 即正經記錄鎖、LOCK_INSERT_INTENTION 即插入意向鎖。此外,十進制的 256 表示 LOCK_WAIT,因此當第 9 個比特位為 0 與 1 分別表示當前事務獲取到鎖與未獲取到鎖處于等待狀態。
  • 其他信息:為了更好的管理系統運行過程中生成的各種鎖結構而設計了各種哈希表和鏈表,可以先忽略;
  • 一堆比特位:比特位的數量是由上面提到的 n_bits 屬性表示,頁面中的每條記錄在記錄頭信息中都包含一個 heap_no 屬性,偽記錄 Infimum 的 heap_no 值為0,Supremum 的 heap_no 值為 1,之后每插入一條記錄,heap_no 值就增 1。鎖結構最后的一堆比特位就對應著一個頁面中的記錄,一個比特位映射一個 heap_no。

文中案例update t2 set name='d' where id=1;這條 update 語句執行時鎖結構中信息如下所示。

---TRANSACTION 11309020, ACTIVE 161 sec
2 lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
TABLE LOCK table `test_zk`.`t2` trx id 11309020 lock mode IX
RECORD LOCKS space id 190 page no 3 n bits 80 index PRIMARY of table `test_zk`.`t2` trx id 11309020 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0

其中:

  • Space ID = 190、Page Number = 3、n_bits = 80、index = PRIMARY
  • type_mode = LOCK_X | LOCK_REC | LOCK_REC_NOT_GAP = 3 | 32 | 1024
  • heap no 2,表明表中的第一行記錄被鎖定;
  • n_fields 4,含義還不確定。

鎖等待時顯示 2 lock struct(s),表示 trx->trx_locks 鎖鏈表的長度為2,每個鏈表節點代表該事務持有的一個鎖結構,包括表鎖,記錄鎖以及自增鎖等。

LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)

其中:

  • LOCK WAIT 2 lock struct(s) 表示事務正在等待鎖,其中鎖鏈表的長度為 2,并非表示在等待兩把鎖;
  • 2 locks 表示 IX 鎖和 lock_mode X locks rec but not gap 即 Record Lock。

小技巧

鎖等待分析

分析鎖等待時,建議在發生鎖等待的現場關聯查詢分析持鎖與等鎖的事務與 SQL,注意如果鎖等待已超時,就看不到了,SQL 如下所示。

select 
    wt.thread_id waiting_thread_id,
    wt.processlist_id waiting_processlist_id,
    wt.processlist_time waiting_time,
    wt.processlist_info waiting_query,
    bt.thread_id blocking_thread_id,
    bt.processlist_id blocking_processlist_id,
    bt.processlist_time blocking_time,
    c.sql_text blocking_query,
    concat('kill ',bt.processlist_id, ';') sql_kill_blocking_connection
from information_schema.innodb_lock_waits l join information_schema.innodb_trx b
    on b.trx_id = l.blocking_trx_id
join information_schema.innodb_trx w
    on w.trx_id = l.requesting_trx_id
join performance_schema.threads wt
    on w.trx_mysql_thread_id=wt.processlist_id
join performance_schema.threads bt
    on b.trx_mysql_thread_id=bt.processlist_id
join performance_schema.events_statements_current c
    on bt.thread_id=c.thread_id \\G

PS.data_locks

從 MySQL 8.0.1 版本開始,可以通過 performance_schema.data_locks 表查看 SQL 執行過程中需要獲取的鎖。

select * from performance_schema.data_locks \\G

上文中提到,只有當事務因為獲取不到鎖而被阻塞即發生鎖等待時 information_schema.innodb_locks 表中才會有記錄,而 performance_schema.data_locks 表中即使事務沒有被阻塞,也可以看到事務持有的鎖,這一點對于鎖分析非常有用。

查看 update 這條 SQL 執行需要獲取的鎖。

mysql> select * from performance_schema.data_locks \\G
Empty set (0.00 sec)

mysql> update t2 set name='d' where id=1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from performance_schema.data_locks \\G
*************************** 1. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140123070938328:1070:140122972540608
ENGINE_TRANSACTION_ID: 2032017
            THREAD_ID: 64
             EVENT_ID: 26
        OBJECT_SCHEMA: test_zk
          OBJECT_NAME: t2
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: NULL
OBJECT_INSTANCE_BEGIN: 140122972540608
            LOCK_TYPE: TABLE  # 表級鎖
            LOCK_MODE: IX  # X 型意向鎖
          LOCK_STATUS: GRANTED
            LOCK_DATA: NULL
*************************** 2. row ***************************
               ENGINE: INNODB
       ENGINE_LOCK_ID: 140123070938328:8:4:2:140122972537552
ENGINE_TRANSACTION_ID: 2032017
            THREAD_ID: 64
             EVENT_ID: 26
        OBJECT_SCHEMA: test_zk
          OBJECT_NAME: t2
       PARTITION_NAME: NULL
    SUBPARTITION_NAME: NULL
           INDEX_NAME: PRIMARY  # 主鍵索引
OBJECT_INSTANCE_BEGIN: 140122972537552
            LOCK_TYPE: RECORD  # 行級鎖
            LOCK_MODE: X,REC_NOT_GAP  # X 型記錄鎖
          LOCK_STATUS: GRANTED
            LOCK_DATA: 1  # 鎖定主鍵值為1的記錄
2 rows in set (0.00 sec)

結果顯示 update 操作需要獲取兩把鎖,包括表級別的意向排它鎖與行級別 X 鎖(RECORD LOCK),與上文中分析結論一致。

上文中查看 INNODB_LOCKS 與 INNODB_LOCK_WAITS 表中均有告警 1 warning,如下所示查看告警。

mysql> show warnings;
+---------+------+------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                  |
+---------+------+------------------------------------------------------------------------------------------+
| Warning | 1681 | 'INFORMATION_SCHEMA.INNODB_LOCKS' is deprecated and will be removed in a future release. |
+---------+------+------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> show warnings;
+---------+------+-----------------------------------------------------------------------------------------------+
| Level   | Code | Message                                                                                       |
+---------+------+-----------------------------------------------------------------------------------------------+
| Warning | 1681 | 'INFORMATION_SCHEMA.INNODB_LOCK_WAITS' is deprecated and will be removed in a future release. |
+---------+------+-----------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

實際上,這兩張表在 5.7.14 版本中已過時,8.0.1 版本中已刪除。

This table is deprecated as of MySQL 5.7.14 and is removed in MySQL 8.0.

其中:

  • INFORMATION_SCHEMA.INNODB_LOCKS 被 performance_schema.data_locks 代替;
  • INFORMATION_SCHEMA.INNODB_LOCK_WAITS 被 data_lock_waitsdata_lock_waits 代替。

結論

鎖本質是是一種并發控制手段,用于解決事務在并發執行時可能引發的一致性問題。

寫-寫操作會導致臟寫,即一個事務覆蓋另一個事務未提交的更改,因此需要給寫操作加寫鎖。

InnoDB 存儲引擎支持事務與行鎖,其中行鎖是給索引項加鎖。

對一條記錄加鎖的本質是在內存中創建一個鎖結構與之關聯(隱式鎖除外)。如果有多個鎖,保存在鏈表結構中。

鎖結構中主要包括 trx 信息與 is_waiting 屬性,分別表示鎖所在的事務信息與當前事務是否在等待,然后將鎖結構與行記錄關聯。

InnoDB 中鎖的實現是悲觀鎖,先加鎖后訪問,因此無論是否獲取到鎖,都會在內存中生成對應的鎖結構,其中 is_waiting 為 false 表示持鎖,為 true 表示等鎖。

因此,并發 update 會導致鎖等待,分析鎖等待的方法主要包括:

  • 使用 information_schema 數據庫中的表獲取鎖信息,不過要求鎖等待現場查看;
  • 使用 SHOW ENGINE INNODB STATUS 獲取鎖信息,不過信息不全,因此適合死鎖分析。

從 MySQL 8.0.1 版本開始,可以通過 performance_schema.data_locks 表查看 SQL 執行過程中需要獲取的鎖。即使事務沒有被阻塞,也可以看到事務持有的鎖,這一點對于鎖分析非常有用。

通過查詢 performance_schema.data_locks 表,可以明確的看到 update 操作需要獲取兩把鎖,包括表級別的意向排它鎖與行級別 X 鎖(RECORD LOCK)。

待辦

  • 鎖的類型
  • 鎖的信息,n_bits、n_fields
  • 死鎖分析
  • 事務隔離級別、MVCC 與鎖的關系
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • SQL
    SQL
    +關注

    關注

    1

    文章

    760

    瀏覽量

    44078
  • MySQL
    +關注

    關注

    1

    文章

    802

    瀏覽量

    26446
  • Hash算法
    +關注

    關注

    0

    文章

    43

    瀏覽量

    7379
收藏 人收藏

    評論

    相關推薦

    深度剖析MySQL/InnoDB的并發控制和加鎖技術

    本文主要是針對MySQL/InnoDB的并發控制和加鎖技術做一個比較深入的剖析,并且對其中涉及到的重要的概念,如多版本并發控制(MVCC),臟讀(dirty read),幻讀(phantom
    的頭像 發表于 10-29 14:36 ?2124次閱讀
    深度剖析<b class='flag-5'>MySQL</b>/InnoDB的<b class='flag-5'>并發</b>控制和加鎖技術

    基于MySQL機制

    在數據庫系統中,為了保證數據的一致性和并發控制,機制發揮著至關重要的作用。尤其在關系型數據庫MySQL中,其獨特的機制設計更是贏得了許多開發者的喜愛。 本文將詳細探討
    的頭像 發表于 09-30 11:16 ?847次閱讀

    樹莓派MYSQL安裝【附離線安裝mysql

    apt-get update3.5、輸入指令apt-get install mysql-server,綠色版安裝會提示2次輸入y回車就行了,同在線安裝方式然后就是等待,在安裝的過程中會提示你讓你輸入
    發表于 03-03 12:14

    由于InnoDB MVCC導致并發BUG介紹

    [原]記錄一個由于InnoDB MVCC導致并發BUG
    發表于 07-17 09:46

    MySQLupdate修改數據與原數據相同是否會再次執行

    本文主要測試MySQL執行update語句時,針對與原數據(即未修改)相同的update語句會在MySQL內部重新執行嗎?
    的頭像 發表于 05-06 10:45 ?3698次閱讀
    <b class='flag-5'>MySQL</b>中<b class='flag-5'>update</b>修改數據與原數據相同是否會再次執行

    select語句和update語句分別是怎么執行的

    一樣,但是具體的實現還是有區別的。 當然深入了解select和update的具體區別并不是只為了面試,當希望Mysql能夠高效的執行的時候,最好的辦法就是清楚的了解Mysql是如何執行查詢的,只有更加全面的了解SQL執行的每一個
    的頭像 發表于 11-03 09:41 ?3527次閱讀
    select語句和<b class='flag-5'>update</b>語句分別是怎么執行的

    Linux中的傷害/等待互斥介紹

    、 rw_semaphore 、 spinlock和rwlock。第一次聽說ww_mutex,在百度上查找的時候發現介紹文檔很少,于是自己學習,寫成筆記。 在某些場合必須同時持有多個,并且獲取的順序可能
    的頭像 發表于 11-06 17:27 ?2627次閱讀

    數據庫的機制真正的原理

    MySQL數據庫中,為了解決并發問題,引入了很多的機制,很多時候,數據庫的是在有數據庫操作的過程中自動添加的。所以,這就導致很多程序員
    的頭像 發表于 11-12 09:33 ?2236次閱讀

    select......for update表還是行?

    驗證 結合一下實例驗證 結果 ? select查詢語句是不會加鎖的,但是select .......for update除了有查詢的作用外,還會加鎖呢,而且它是悲觀。 那么它加的是行還是表
    的頭像 發表于 10-10 15:54 ?1488次閱讀

    MySQL是怎么加行級的?有什么規則?

    是不是很多人都對 MySQL 加行級的規則搞的迷迷糊糊,對記錄一會加的是 next-key ,一會加是間隙,一會又是記錄
    的頭像 發表于 11-17 09:28 ?780次閱讀

    一文徹底搞懂MySQL究竟的啥1

    MySQL系列文章已經鴿了挺久了,最近趕緊擠了擠時間,和大家聊一聊MySQL。 只要學計算機,「``」永遠是一個繞不過的話題。
    的頭像 發表于 03-03 10:12 ?444次閱讀
    一文徹底搞懂<b class='flag-5'>MySQL</b><b class='flag-5'>鎖</b>究竟<b class='flag-5'>鎖</b>的啥1

    一文徹底搞懂MySQL究竟的啥2

    MySQL系列文章已經鴿了挺久了,最近趕緊擠了擠時間,和大家聊一聊MySQL。 只要學計算機,「``」永遠是一個繞不過的話題。
    的頭像 發表于 03-03 10:13 ?420次閱讀
    一文徹底搞懂<b class='flag-5'>MySQL</b><b class='flag-5'>鎖</b>究竟<b class='flag-5'>鎖</b>的啥2

    MySQL并發Replace into導致死鎖場景簡析

    在之前的文章 #issue 68021 MySQL unique check 問題中, 我們已經介紹了在 MySQL 里面, 由于唯一鍵的檢查(unique check), 導致
    的頭像 發表于 06-13 10:56 ?1363次閱讀
    <b class='flag-5'>MySQL</b><b class='flag-5'>并發</b>Replace into<b class='flag-5'>導致</b>死鎖場景簡析

    java redis處理并發代碼

    問題。 本文將詳細介紹如何在Java代碼中使用Redis實現并發代碼的處理。我們將分為以下幾個方面來討論: Redis分布式的原理 Redis分布式
    的頭像 發表于 12-04 11:04 ?924次閱讀

    導致MySQL索引失效的情況以及相應的解決方法

    導致MySQL索引失效的情況以及相應的解決方法? MySQL索引的目的是提高查詢效率,但有些情況下索引可能會失效,導致查詢變慢或效果不如預期。下面將詳細
    的頭像 發表于 12-28 10:01 ?731次閱讀