mysql小白系列_07 鎖與事務

1.MySQL參數autocommit生產環境設1仍是0?爲何?html

2.MySQL參數tx_isolation生產環境上大多數是設什麼值,爲何?mysql

3.與MySQL鎖相關的有哪些因素?sql


1.MySQL參數autocommit生產環境設1仍是0?爲何?

mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.00 sec)
  • 查看select @@autocommit;
  • 臨時修改set autocommit=0;
  • 自動提交,innodb引擎特有,未提交的數據僅存在buffer中
  • 提交後的數據寫入數據文件
  • 使用了begin;,autocommit參數不生效
  • 建議設置爲1,而且使用三段式寫法begin...dml...commit;
  • select帶有metadata lock,若是爲0(未提交),會阻塞DDL操做

2.MySQL參數tx_isolation生產環境上大多數是設什麼值,爲何?

https://dev.mysql.com/doc/refman/5.6/en/innodb-transaction-isolation-levels.html#isolevel_serializable數據庫

mysql> show variables like '%tx_isolation%';
+---------------+-----------------+
| Variable_name | Value           |
+---------------+-----------------+
| tx_isolation  | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.00 sec)
  • 讀未提交 READ-UNCOMMITED
  • 讀已提交 READ-COMMITTED
  • 可重複讀 REPEATABLE-READ
  • 串行化 SERIALIZABLE
讀未提交 READ-UNCOMMITED

一個事務能夠讀取其餘事務未提交的結果
容許髒讀、幻讀、不可重複讀,隔離級別最低,併發最高
實際應用基本不用session

  1. A會話設置事務隔離級別爲READ-UNCOMMITTED
mysql A>set tx_isolation='READ-UNCOMMITTED';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql A>select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
  1. A/B會話查看相同的表數據,此時表數據一致
mysql A>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw  |
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

mysql B>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw  |
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)
  1. B會話更新記錄但不提交
mysql B>start transaction;
Query OK, 0 rows affected (0.00 sec)

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

mysql B>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw1 |
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)
  1. A會話查看結果
mysql A>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw1 |
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

此時A會話讀到了B會話開啓的未提交的事務併發

  1. B會話執行rollback
mysql B>rollback;
Query OK, 0 rows affected (0.00 sec)

mysql B>
mysql B>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw  |
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)
  1. A會話再查看數據
mysql A>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw  |
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

B會話回滾後,A會話讀到了初始數據oracle

讀已提交 READ-COMMITTED
  • 一個事務能讀取其餘事務已經提交的結果,與上面相反
  • 用得最多,併發高,生產環境使用此級別,
  • oracle/mssql/pg用的是此級別
  • 沒法解決不可重複讀和幻讀
  1. A會話設置事務隔離級別爲READ-COMMITTED
mysql A>set tx_isolation='READ-COMMITTED';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql A>SELECT @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set, 1 warning (0.00 sec)
  1. A/B會話查看相同的表數據,此時表數據一致
mysql A>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw  |
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

mysql B>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw  |
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)
  1. B會話更新記錄但不提交
mysql B>start transaction;
Query OK, 0 rows affected (0.00 sec)

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

mysql B>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw1 |
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

此時B會話已經看到數據更改高併發

  1. A會話查看結果
mysql A>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw  |
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

此時A會話沒法讀到B會話開啓的未提交的事務測試

  1. B會話執行commit;
mysql B>commit;
Query OK, 0 rows affected (0.02 sec)

mysql B>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw1 |
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)
  1. A會話再查看數據
mysql A>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw1  |
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

B會話提交後,完成一個事務,A會話讀取到B會話作的提交優化

可重複讀 REPEATABLE-READ

能夠讀取到已提交的結果,mysql默認級別
併發能力比讀已提交稍弱,真正的隔離級別,並解決了讀已提交沒法解決的不可重複讀和幻讀的問題
與READ_COMMITTED區別是,REPEATABLE-READ開啓的狀況下,不管其餘事務是否提交了新的變動,同一條件的查詢返回結果都是一致的
也就是即便數據更新了,原來會話的事務中,相同的查詢條件先後查看的結果同樣

  1. B會話更新記錄但不提交
mysql B>start transaction;
Query OK, 0 rows affected (0.00 sec)

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

mysql B>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw11|
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

此時B會話已經看到數據更改

  1. A會話設置事務隔離級別爲REPEATABLE-READ
mysql A>set tx_isolation='REPEATABLE-READ';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql A>SELECT @@tx_isolation;
+-----------------+
| @@tx_isolation  |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)

mysql A>start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql A>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw1 |
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

此時A會話沒法讀到B會話開啓的未提交的事務

  1. B會話執行commit;
mysql B>commit;
Query OK, 0 rows affected (0.02 sec)

mysql B>select * from t1;
+------+-------+
| id   | name  |
+------+-------+
|    1 | yzw11 |
|    2 | yzw2  |
+------+-------+
2 rows in set (0.00 sec)
  1. A會話再查看數據
mysql A>select * from t1;
+------+------+
| id   | name |
+------+------+
|    1 | yzw1 |
|    2 | yzw2 |
+------+------+
2 rows in set (0.00 sec)

B會話提交後,完成一個事務,可是在A會話的同一個事務中,仍是沒法讀取到B會話作的提交

  1. A會話此時提交事務
mysql A>commit;
Query OK, 0 rows affected (0.00 sec)

mysql A>select * from t1;
+------+-------+
| id   | name  |
+------+-------+
|    1 | yzw11 |
|    2 | yzw2  |
+------+-------+
2 rows in set (0.00 sec)

A會話提交後,能夠讀取到B會話作的提交

串行化 SERIALIZABLE

與REPEATABLE-READ類似,只會讀取其餘事務已經提交的內容
不一樣的是,若是autocommit爲false,select會隱式轉化爲select ... lock in share mode

  1. B會話更新記錄但不提交
mysql B>start transaction;
Query OK, 0 rows affected (0.00 sec)

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

mysql B>select * from t1;
+------+--------+
| id   | name   |
+------+--------+
|    1 | yzw111 |
|    2 | yzw2   |
+------+--------+
2 rows in set (0.00 sec)

此時B會話已經看到數據更改

  1. A會話設置事務隔離級別爲SERIALIZABLE,而且修改autocommit=0,再查看被修改的數據
mysql A>set tx_isolation='SERIALIZABLE';
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql A>SELECT @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE   |
+----------------+
1 row in set, 1 warning (0.00 sec)

mysql A>set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql A>select * from t1;

此時A會話被阻塞,由於隱式加了lock in share mode

  1. B會話執行commit;
mysql B>commit;
Query OK, 0 rows affected (0.02 sec)

mysql B>select * from t1;
+------+--------+
| id   | name   |
+------+--------+
|    1 | yzw111 |
|    2 | yzw2   |
+------+--------+

2 rows in set (0.00 sec)
  1. A會話再查看數據
mysql A>select * from t1;
+------+--------+
| id   | name   |
+------+--------+
|    1 | yzw111 |
|    2 | yzw2   |
+------+--------+

2 rows in set (0.00 sec)
4種隔離級別能解決的併發問題
隔離級別 dirty read(髒讀) unrepeatable read(不可重複讀) phantom read(幻讀)
read uncommitted Y Y Y
read committed X Y Y
reapeatable read X X X
serializable X X X

3.與MySQL鎖相關的有哪些因素?

鎖 用於多個事務訪問同一個對象時根據這些操做訪問同一對象的前後次序給事務排序

  • redo和undo解決原子性A
  • undo解決一致性C
  • lock解決隔離性I
  • redo解決次就行D
鎖的4種模式
  1. IS-意向共享鎖 事務打算給數據行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的IS鎖,給表加鎖
  2. IX-意向排它鎖 事務打算給數據行加行排它鎖,事務在給一個數據行加排它鎖前必須先取得該表的IX鎖,給表加鎖
  3. S-共享鎖 容許一個事務去讀一行,阻止其餘事務得到相同數據集的排它鎖,行鎖,阻止排它鎖
  4. X-排它鎖 容許得到排它鎖的事務更新數據,阻止其餘事務取得相同數據集的共享鎖和排他寫鎖
鎖兼容性列表
兼容性 IS S IX X
IS 兼容 兼容 兼容 衝突
S 兼容 兼容 衝突 衝突
IX 兼容 衝突 兼容 衝突
X 衝突 衝突 衝突 衝突
行鎖的3種範圍
  1. record lock-行記錄鎖
  2. gap lock-間隙鎖 針對普通索引(二級索引)
  3. next-key lock-下一鍵鎖,索引記錄鎖以及索引記錄之間的間隙鎖,兩者的組合鎖
不一樣數據庫鎖的實現
DB lock type
innodb 行級鎖
oracle 行級鎖
myisam 表鎖
MSSQL 行級鎖、鎖升級
lock與latch的區別
type lock latch
對象 事務 線程
保護 數據庫對象 內存結構對象
持續時間 長-毫秒級 短-微秒級
模式 表鎖行鎖 互斥(0/1)
死鎖

顯式加鎖

  • S select * from tabname where ... lock in share mode;
  • X select * from tabname where ... for update;

意向鎖是innodb引擎本身增長,不須要干預
update/delete/insert自動加X鎖,普通select不加任何鎖

共享鎖例子
session A session B
myslq A>set autocommit=0;<br>Query OK, 0 rows affected (0.00 sec)<br><br>myslq A>select * from t1 where id=1;<br>+------+-------+-------+<br>| id | name1 | name2 |<br>+------+-------+-------+<br>| 1  | yzw1    | yzw11   |<br>+------+-------+-------+ <br>1 row in set (0.00 sec) mysql B>set autocommit=0;<br> Query OK, 0 rows affected (0.00 sec)<br><br>myslq A>select * from t1 where id=1;<br>+------+-------+-------+<br>| id | name1 | name2 |<br>+------+-------+-------+<br>| 1  | yzw1    | yzw11   |<br>+------+-------+-------+ <br>1 row in set (0.00 sec)
==session A給ID=1的記錄增長S鎖==:<br>myslq A>select * from t1 where id=1 lock in share mode;<br>+------+-------+-------+<br>| id | name1 | name2 |<br>+------+-------+-------+<br>| 1  | yzw1    | yzw11   |<br>+------+-------+-------+ <br>1 row in set (0.00 sec)  
  ==session B進行查詢並加S鎖==:<br>myslq A>select * from t1 where id=1 lock in share mode;<br>+------+-------+-------+<br>| id | name1 | name2 |<br>+------+-------+-------+<br>| 1  | yzw1    | yzw11   |<br>+------+-------+-------+ <br>1 row in set (0.00 sec)<br>S鎖兼容,並不會互相阻塞
==session A更新記錄,此時給表加IX鎖,並給行加X鎖:==<br>myslq A>update t1 set name1='YZW1' where id=1;<br>由於session B此時持有S鎖,session A沒法加上X鎖,所以進入等待X鎖  
  ==session B也對一樣的行進行更新操做:==<br>mysql B>update t1 set name2='YZW11' where id=1;<br>ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction<br>檢測到死鎖退出
==session A得到X鎖後,更新成功:==<br>Query OK, 1 row affected (1.39 sec)<br>Rows matched: 1 Changed: 1 Warnings: 0  
排它鎖例子
session A session B
mysql A>set autocommit=0;<br>Query OK, 0 rows affected (0.00 sec)<br><br>mysql A>select * from t1 where id=2;<br>+------+------+<br>| id     | name  |<br>+------+------+<br>|   2    | yzw2   |<br>+------+------+<br>1 row in set (0.00 sec) mysql A>set autocommit=0;<br>Query OK, 0 rows affected (0.00 sec)<br><br>mysql A>select * from t1 where id=2;<br>+------+------+<br>| id     | name  |<br>+------+------+<br>|   2    | yzw2   |<br>+------+------+<br>1 row in set (0.00 sec)
==session A加X鎖:==<br>mysql A>select * from t1 where id=2 for update;<br>+------+------+<br>| id     | name  |<br>+------+------+<br>|   2    | yzw2   |<br>+------+------+<br>1 row in set (0.00 sec)  
  ==session B能夠進行正常查詢:==<br>mysql A>select * from t1 where id=2;<br>+------+------+<br>| id     | name  |<br>+------+------+<br>|   2    | yzw2   |<br>+------+------+<br>1 row in set (0.00 sec)
  ==加S鎖被阻塞,鎖超時退出:==<br>mysql B>select * from t1 where id=2 lock in share mode;<br>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
  ==加X鎖被阻塞,鎖超時退出:==<br>mysql B>select * from t1 where id=2 for update;<br>ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
==對鎖定記錄進行操做並提交,釋放鎖:==<br>mysql A>update t1 set name='YZW22' where id=2;<br>Query OK, 1 row affected (0.00 sec)<br>Rows matched: 1 Changed: 1 Warnings: 0<br><br>mysql A>commit;<br>Query OK, 0 rows affected (0.01 sec)  
  session A釋放鎖後,session B能夠正常加S鎖或者X鎖,且不會被阻塞


行鎖分析
1. 建立測試數據
create table t2(id int,name varchar(10),primary key(id),key(name));
insert into t2 values(1,'A'),(3,'A'),(5,'C'),(7,'G'),(10,'I');
mysql> select * from t2;
+----+------+
| id | name |
+----+------+
|  1 | A    |
|  3 | A    |
|  5 | C    |
|  7 | G    |
| 10 | I    |
+----+------+
5 rows in set (0.00 sec)
  • 這裏的GAP lock一共有6個,1,5,10先後各1個
  • record lock一共有6個,1,3,5,7,10
  • next-key lock,好比1,包含1前面的0和1後面的2,也就是2個間隙

2.測試record lock

測試1

  • session A
mysql A>begin;
Query OK, 0 rows affected (0.00 sec)

mysql A>select * from t2 where NAME='C' for update;
+----+------+
| id | name |
+----+------+
|  5 | C    |
+----+------+
1 row in set (0.00 sec)

name是普通/二級索引,且是RR級別隔離,最終在主鍵索引上會給id=5加上record lock,先後加上gap lock

  • session B
mysql B>begin;
Query OK, 0 rows affected (0.00 sec)

mysql B>select * from t2 where id=5 lock in share mode;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

session A的X鎖和session B的S鎖衝突

mysql> select * from information_schema.innodb_locks;
+------------------------+-----------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| lock_id                | lock_trx_id     | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+------------------------+-----------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| 421469287688928:45:3:4 | 421469287688928 | S         | RECORD    | `db1`.`t2` | PRIMARY    |         45 |         3 |        4 | 5         |
| 19207:45:3:4           | 19207           | X         | RECORD    | `db1`.`t2` | PRIMARY    |         45 |         3 |        4 | 5         |
+------------------------+-----------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
2 rows in set, 1 warning (0.00 sec)

測試2

  • session A
mysql A>begin;
Query OK, 0 rows affected (0.00 sec)

mysql A>select * from t2 where id=5 and name='C' for update;
+----+------+
| id | name |
+----+------+
|  5 | C    |
+----+------+
1 row in set (0.00 sec)

記錄存在,加X鎖,record lock 和先後 gap lock

  • session B
mysql B>begin;
Query OK, 0 rows affected (0.00 sec)

mysql B>select * from t2 where id=5 and name='B' for update;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

記錄不存在,位置在C的前面,C先後已經被加了gap lock,所以被阻塞

mysql> select * from information_schema.innodb_locks;
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| lock_id      | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| 19210:45:3:4 | 19210       | X         | RECORD    | `db1`.`t2` | PRIMARY    |         45 |         3 |        4 | 5         |
| 19209:45:3:4 | 19209       | X         | RECORD    | `db1`.`t2` | PRIMARY    |         45 |         3 |        4 | 5         |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
2 rows in set, 1 warning (0.00 sec)

3.測試gap lock

測試1

  • session A
mysql A>begin;
Query OK, 0 rows affected (0.00 sec)

mysql A>select * from t2 where name='C' for update;
+----+------+
| id | name |
+----+------+
|  5 | C    |
+----+------+
1 row in set (4.18 sec)

給ID=5的行加X鎖,同時給4/6加間隙鎖

  • session B
mysql B>insert into t2 values(4,'C');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql B>insert into t2 values(6,'C');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

5先後已經被加了間隙鎖,插入重複的值(4,C)在id=5的前面,(6,C)在id=5的後面,插入被阻塞

mysql> mysql> select * from information_schema.innodb_locks;
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| lock_id      | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| 19219:45:4:4 | 19219       | X,GAP     | RECORD    | `db1`.`t2` | name       |         45 |         4 |        4 | 'C', 5    |
| 19218:45:4:4 | 19218       | X         | RECORD    | `db1`.`t2` | name       |         45 |         4 |        4 | 'C', 5    |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
2 rows in set, 1 warning (0.00 sec)

mysql> select * from information_schema.innodb_locks;
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| lock_id      | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| 19219:45:4:5 | 19219       | X,GAP     | RECORD    | `db1`.`t2` | name       |         45 |         4 |        5 | 'G', 7    |
| 19218:45:4:5 | 19218       | X,GAP     | RECORD    | `db1`.`t2` | name       |         45 |         4 |        5 | 'G', 7    |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
2 rows in set, 1 warning (0.00 sec)

查看誰阻塞誰

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;
+----------------+----------------+------------------------------+-----------------+-----------------+----------------+
| waiting_trx_id | waiting_thread | waiting_query                | blocking_trx_id | blocking_thread | blocking_query |
+----------------+----------------+------------------------------+-----------------+-----------------+----------------+
| 19219          |              2 | insert into t2 values(6,'C') | 19218           |               3 | NULL           |
+----------------+----------------+------------------------------+-----------------+-----------------+----------------+
1 row in set, 1 warning (0.00 sec)

測試2

  • session A
mysql A>begin;
Query OK, 0 rows affected (0.00 sec)

mysql A>select * from t2 where name='C' for update;
+----+------+
| id | name |
+----+------+
|  5 | C    |
+----+------+
1 row in set (0.00 sec)

主鍵5加X鎖,主鍵4/六、二級索引B/D/E/F加間隙鎖

  • session B
mysql B>begin;
Query OK, 0 rows affected (0.00 sec)

mysql B>insert into t2 values(2,'B');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql B>insert into t2 values(8,'F');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql B>insert into t2 values(6,'G');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql B>insert into t2 values(2,'A');
Query OK, 1 row affected (0.00 sec)

mysql B>insert into t2 values(8,'D');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql B>insert into t2 values(8,'E');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql B>insert into t2 values(8,'F');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql B>insert into t2 values(11,'D');
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

RR(REPEATABLE-READ)模式纔會出現這種狀況,避免出現幻讀



RR模式下有哪些鎖測試例子
  1. 建立測試數據
create table t4( \
id int(11) not null default '0', \
user_id int(11) default null, \
user_name varchar(10) default null, \
create_time varchar(10) default null, \
address varchar(10) default null, \
primary key(id), \
key idx_time_name (create_time,user_name) );

insert into t4 values(1,101,'tom','20161010',''), \
(4,104,'joe','20160305',''), \
(6,106,'tom','20161231',''), \
(8,108,'tom','20160515','sh'), \
(10,110,'tom','20160101',''), \
(100,200,'jack','20161120','');

mysql A>select * from t4;
+-----+---------+-----------+-------------+---------+
| id  | user_id | user_name | create_time | address |
+-----+---------+-----------+-------------+---------+
|   1 |     101 | tom       | 20161010    |         |
|   4 |     104 | joe       | 20160305    |         |
|   6 |     106 | tom       | 20161231    |         |
|   8 |     108 | tom       | 20160515    | sh      |
|  10 |     110 | tom       | 20160101    |         |
| 100 |     200 | jack      | 20161120    |         |
+-----+---------+-----------+-------------+---------+
6 rows in set (0.00 sec)

id爲主鍵,create_time和user_name爲組合索引

select * from t4 \
where create_time > '20160101' \
and create_time < '20161120' \
and user_name='tom' \
and address != '' for update;

mysql A>select * from t4 \
    -> where create_time > '20160101' \
    -> and create_time < '20161120' \
    -> and user_name='tom' \
    -> and address != '' for update;
+----+---------+-----------+-------------+---------+
| id | user_id | user_name | create_time | address |
+----+---------+-----------+-------------+---------+
|  8 |     108 | tom       | 20160515    | sh      |
+----+---------+-----------+-------------+---------+
1 row in set (0.00 sec)

最終符合條件的只有1條數據

借用一張圖分析 image

  • 這裏索引走的是普通組合索引,所以根據create_time首先定位到3條數據,根據user_name定位到最終1條數據

mysql是聚簇索引,數據存放在主鍵索引的葉子節點上,二級索引只保存索引結構,最終定位回主鍵索引

  • 在idx_time_name索引上根據create_time升序排序,給3條數據加了X鎖,總共4個gap lock
  • 從二級索引定位到主鍵索引上的1/4/8三條數據加了X鎖,主鍵索引上不考慮gap lock
MDL鎖

metadata lock 用於解決或者保證DDL操做與DML操做之間一致性,在Server級實現

  • session A
mysql A>begin;
Query OK, 0 rows affected (0.00 sec)

mysql A>select * from t1;
+------+--------+
| id   | name   |
+------+--------+
|    1 | yzw111 |
|    2 | YZW22  |
+------+--------+
2 rows in set (0.00 sec)
  • session B
mysql B>begin;
Query OK, 0 rows affected (0.00 sec)

mysql B>drop table t1;
  • session C
mysql C>select * from t1;
ERROR 1146 (42S02): Table 'db1.t1' doesn't exist

此時B/C會話阻塞,由於A持有MDL鎖,鎖模式爲shared_read,B持有X鎖
一旦Acommit,B成功drop掉表,C報表不存在

解決meta data lock
  • 減小線上DDL操做
  • 線上DB不要隨便作alter table;
  • kill掉DDL會話
mysql> show processlist;
+----+------+-----------+------+---------+------+---------------------------------+------------------+
| Id | User | Host      | db   | Command | Time | State                           | Info             |
+----+------+-----------+------+---------+------+---------------------------------+------------------+
|  4 | root | localhost | db1  | Query   |   16 | Waiting for table metadata lock | drop table t1    |
|  7 | root | localhost | db1  | Sleep   |   22 |                                 | NULL             |
|  8 | root | localhost | db1  | Query   |   12 | Waiting for table metadata lock | select * from t1 |
|  9 | root | localhost | NULL | Query   |    0 | starting                        | show processlist |
+----+------+-----------+------+---------+------+---------------------------------+------------------+
4 rows in set (0.00 sec)

mysql> kill 7;
Query OK, 0 rows affected (0.00 sec)
死鎖
  • 產生迴路 兩個或者兩個以上的事務執行過程當中,分別持有對方須要的鎖
  • 加鎖順序不一致 兩個或者兩個以上事務同一時刻併發執行,因爭奪資源而形成的一種互相等待
迴路
  • 建立數據
mysql A>create table t1( id int(11) not null default '0', \
    -> name varchar(10) default null, \
    -> primary key (id));
Query OK, 0 rows affected (0.03 sec)

mysql A>insert into t1 values (1,'AAAA'),(2,'BBBB');
Query OK, 2 rows affected (0.10 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql A>select * from t1;
+----+------+
| id | name |
+----+------+
|  1 | AAAA |
|  2 | BBBB |
+----+------+
2 rows in set (0.00 sec)
session A session B
mysql A>begin;<br>Query OK, 0 rows affected (0.00 sec)<br><br>mysql A>update t1 set name='aaaa' where id=1;<br>Query OK, 1 row affected (0.00 sec)<br>Rows matched: 1 Changed: 1 Warnings: 0 <br>持有一個X鎖  
  mysql B>begin;<br>Query OK, 0 rows affected (0.00 sec)<br><br>mysql B>update t1 set name='bbbb' where id=2;<br>Query OK, 1 row affected (0.00 sec)<br>Rows matched: 1 Changed: 1 Warnings: 0 <br>也持有一個X鎖
mysql A>update t1 set name='CCCC' where id=2;<br>Query OK, 1 row affected (0.77 sec)<br>Rows matched: 1 Changed: 1 Warnings: 0 <br>再得到一個X鎖  
  mysql B>update t1 set name='DDDD' where id=1;<br>ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction<br>產生死鎖
併發

借用一張圖 image

  • 第一個session根據二級索引name查詢加X鎖,首先給id=1的記錄加鎖
  • 第二個session根據二級索引age查詢加X鎖,首先給id=6的記錄加鎖

此時兩個session準備給各自第二條記錄加X鎖時,發現已經有X鎖了,各自進入阻塞狀態

減小死鎖的方法
  1. 數據庫自動檢測死鎖,優先回滾小事務
  2. innodb_lock_wait_timeout = 3
  3. 儘快提交事務,事務粒度越小越不容易發生死鎖
  4. 下降隔離級別,使用RC提升併發下降死鎖機率
  5. 多行記錄、跨表時,保持事務操做順序
  6. 優化索引、SQL,減小掃描/鎖範圍,下降機率
查找鎖
  1. show engine innodb status\G;

只能查看最後一個死鎖的信息

------------------------
LATEST DETECTED DEADLOCK
------------------------
2018-02-24 01:28:01 0x7f91647b7700
*** (1) TRANSACTION:
TRANSACTION 18793, ACTIVE 38 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 10, OS thread handle 140262433064704, query id 257 localhost root updating
update t1 set name='CCCC' where id=2
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 48 page no 3 n bits 72 index PRIMARY of table `db1`.`t1` trx id 18793 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;
 1: len 6; hex 00000000496a; asc     Ij;;
 2: len 7; hex 4f000001a602fb; asc O      ;;
 3: len 4; hex 62626262; asc bbbb;;

*** (2) TRANSACTION:
TRANSACTION 18794, ACTIVE 37 sec starting index read
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 4, OS thread handle 140262432798464, query id 258 localhost root updating
update t1 set name='DDDD' where id=1
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 48 page no 3 n bits 72 index PRIMARY of table `db1`.`t1` trx id 18794 lock_mode X locks rec but not gap
Record lock, heap no 3 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
 0: len 4; hex 80000002; asc     ;;
 1: len 6; hex 00000000496a; asc     Ij;;
 2: len 7; hex 4f000001a602fb; asc O      ;;
 3: len 4; hex 62626262; asc bbbb;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 48 page no 3 n bits 72 index PRIMARY of table `db1`.`t1` trx id 18794 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 000000004969; asc     Ii;;
 2: len 7; hex 4e00000166035b; asc N   f [;;
 3: len 4; hex 61616161; asc aaaa;;
  1. 將全部死鎖信息打印到日誌innodb_print_all_deadlocks
  2. 查看鎖

https://dev.mysql.com/doc/refman/5.6/en/innodb-information-schema-examples.html


sql查詢誰阻塞誰

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;

查看被阻塞的SQL信息

mysql> select trx_id,trx_state,trx_started,trx_wait_started,trx_mysql_thread_id,trx_query from information_schema.innodb_trx;
+--------+-----------+---------------------+---------------------+---------------------+------------------------------+
| trx_id | trx_state | trx_started         | trx_wait_started    | trx_mysql_thread_id | trx_query                    |
+--------+-----------+---------------------+---------------------+---------------------+------------------------------+
| 19219  | LOCK WAIT | 2018-02-25 10:57:44 | 2018-02-25 11:31:39 |                   2 | insert into t2 values(6,'C') |
| 19218  | RUNNING   | 2018-02-25 10:57:35 | NULL                |                   3 | NULL                         |
+--------+-----------+---------------------+---------------------+---------------------+------------------------------+
2 rows in set (0.00 sec)
  • trx_id innodb存儲引擎內部惟一的事務ID
  • trx_state 當前事務的狀態
  • trx_started 當前事務的開始時間
  • trx_wait_started 事務等待開始的時間
  • trx_mysql_thread_id mysql的線程ID,也就是shop processlist的顯示結果
  • trx_query 事務運行的sql語句

查看獲取鎖類型

mysql> select * from information_schema.innodb_locks;
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| lock_id      | lock_trx_id | lock_mode | lock_type | lock_table | lock_index | lock_space | lock_page | lock_rec | lock_data |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
| 19219:45:4:5 | 19219       | X,GAP     | RECORD    | `db1`.`t2` | name       |         45 |         4 |        5 | 'G', 7    |
| 19218:45:4:5 | 19218       | X,GAP     | RECORD    | `db1`.`t2` | name       |         45 |         4 |        5 | 'G', 7    |
+--------------+-------------+-----------+-----------+------------+------------+------------+-----------+----------+-----------+
2 rows in set, 1 warning (0.00 sec)
  • lock_id 鎖的id
  • lock_trx_id 事務id
  • lock_mode 鎖的模式
  • lock_type 鎖的類型,是表鎖仍是行鎖
  • lock_table 要加鎖的表
  • lock_index 鎖的索引
  • lock_space innodb存儲引擎表空間的ID號
  • lock_page 被鎖住的頁的數量,若是是表鎖,該值是NULL
  • lock_rec 被鎖住的行的數量,若是是表鎖,該值是NULL
  • lock_data 被鎖住的行的主鍵值,若是是表鎖,該值是NULL

查看申請資源的ID號

mysql> desc information_schema.innodb_lock_waits;
+-------------------+-------------+------+-----+---------+-------+
| Field             | Type        | Null | Key | Default | Extra |
+-------------------+-------------+------+-----+---------+-------+
| requesting_trx_id | varchar(18) | NO   |     |         |       |
| requested_lock_id | varchar(81) | NO   |     |         |       |
| blocking_trx_id   | varchar(18) | NO   |     |         |       |
| blocking_lock_id  | varchar(81) | NO   |     |         |       |
+-------------------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)

mysql> select * from  information_schema.innodb_lock_waits;
+-------------------+-------------------+-----------------+------------------+
| requesting_trx_id | requested_lock_id | blocking_trx_id | blocking_lock_id |
+-------------------+-------------------+-----------------+------------------+
| 19219             | 19219:45:4:5      | 19218           | 19218:45:4:5     |
+-------------------+-------------------+-----------------+------------------+
1 row in set, 1 warning (0.00 sec)
  • requesting_trx_id 申請鎖資源的事務ID
  • requesting_lock_id 申請的鎖的ID
  • blocking_trx_id 被阻塞的事務ID
  • blocking_lock_id 被阻塞的鎖的ID
相關文章
相關標籤/搜索