1. 程式人生 > >RC與RR隔離級別下MySQL不同的加鎖解鎖方式

RC與RR隔離級別下MySQL不同的加鎖解鎖方式

RC與RR隔離級別下MySQL不同的加鎖解鎖方式
RC隔離級別
RR隔離級別
半一致讀semi-consistent read
半一致讀發生條件
innodb_locks_unsafe_for_binlog
一開始的例子
RC隔離級別
session 1
session 2
RR隔離級別
session 1
session 2
引申:RR隔離級別,且開啟innodb_locks_unsafe_for_binlog=ON
參考

RC與RR隔離級別下MySQL不同的加鎖解鎖方式

  • MySQL5.7.21
  • 資料準備
 
root@localhost : pxs 05:26:27> show create table dots\G
*************************** 1. row ***************************
      Table
: dots
Create Table: CREATE TABLE `dots` (
  `id` int(11) NOT NULL,
  `color` varchar(20) COLLATE utf8_bin NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE
=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin
1 row in set (0.00 sec)
root@localhost : pxs 05:27:34> select * from dots;
+----+-------+
| id | color |
+----+-------+
|  1 | black |
|  2 | white |
|  3 | black |
|  4 | white |
+----+-------+
4 rows in set (0.00 sec)
root@localhost : pxs 01:57:02> show variables like 'innodb_locks_unsafe_for_binlog';
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| innodb_locks_unsafe_for_binlog | OFF  |
+--------------------------------+-------+
1 row in set (0.00 sec)

RC隔離級別

  • 確認隔離級別
 
root@localhost : pxs 05:27:35> show variables like '%iso%';
+-----------------------+----------------+
| Variable_name        | Value          |
+-----------------------+----------------+
| transaction_isolation | READ-COMMITTED |
| tx_isolation          | READ-COMMITTED |
+-----------------------+----------------+
2 rows in set (0.01 sec)
  • 同時開啟兩個會話,按下圖的流程開始操作。

Session 1

Session 2

begin

begin

update dots set color = 'black' where color = 'white';

Query OK, 2 rows affected (0.00 sec)

Rows matched: 2  Changed: 2  Warnings: 0

將color是white的記錄更新為black

 

 

select lock_id,lock_trx_id,lock_mode,lock_type,lock_table,lock_index from information_schema.innodb_locks;

Empty set, 1 warning (0.00 sec)

檢視是否存在鎖衝突

 

 

update dots set color = 'white' where color = 'black';

Query OK, 2 rows affected (0.00 sec)

Rows matched: 2  Changed: 2  Warnings: 0

在另一個會話將color是black的記錄更新為white

select lock_id,lock_trx_id,lock_mode,lock_type,lock_table,lock_index from information_schema.innodb_locks;

Empty set, 1 warning (0.00 sec)

此時再檢視,也不存在鎖衝突

 

select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | black |

|  2 | black |

|  3 | black |

|  4 | black |

+----+-------+

4 rows in set (0.00 sec)

session 1查看錶資料發現color列被更新為black

select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | white |

|  2 | white |

|  3 | white |

|  4 | white |

+----+-------+

4 rows in set (0.01 sec)

session 2查看錶資料發現color列被更新為white

commit;

session 1先提交

 

 

select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | white |

|  2 | black |

|  3 | white |

|  4 | black |

+----+-------+

4 rows in set (0.00 sec)

session 2能看到session 1提交後的變更情況

select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | black |

|  2 | black |

|  3 | black |

|  4 | black |

+----+-------+

4 rows in set (0.00 sec)

 

 

commit

session 2提交

 

select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | white |

|  2 | black |

|  3 | white |

|  4 | black |

+----+-------+

4 rows in set (0.00 sec)

RR隔離級別

  • 確認隔離級別
 
root@localhost : pxs 05:24:41> show variables like '%iso%';
+-----------------------+-----------------+
| Variable_name        | Value          |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
| tx_isolation          | REPEATABLE-READ |
+-----------------------+-----------------+
2 rows in set (0.01 sec)
  • 同時開啟兩個會話,按下圖的流程開始操作。

Session 1

Session 2

begin

 

 

begin

update dots set color = 'black' where color = 'white';

Query OK, 2 rows affected (0.00 sec)

Rows matched: 2  Changed: 2  Warnings: 0

將color是white的記錄更新為black

 

select lock_id,lock_trx_id,lock_mode,lock_type,lock_table,lock_index from information_schema.innodb_locks;

Empty set, 1 warning (0.00 sec)

檢視是否存在鎖衝突 

 

 

[email protected] : pxs 11:31:13> update dots set color = 'white' where color = 'black';

更新被阻塞

select lock_id,lock_trx_id,lock_mode,lock_type,lock_table,lock_index from information_schema.innodb_locks;

+-------------+-------------+-----------+-----------+--------------+------------+

| lock_id     | lock_trx_id | lock_mode | lock_type | lock_table   | lock_index |

+-------------+-------------+-----------+-----------+--------------+------------+

| 2925:29:3:2 | 2925        | X         | RECORD    | `pxs`.`dots` | PRIMARY    |

| 2924:29:3:2 | 2924        | X         | RECORD    | `pxs`.`dots` | PRIMARY    |

+-------------+-------------+-----------+-----------+--------------+------------+

2 rows in set, 1 warning (0.00 sec)

此時再檢視,確實存在鎖衝突

 

[email protected] : pxs 11:34:06> select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | black |

|  2 | black |

|  3 | black |

|  4 | black |

+----+-------+

4 rows in set (0.00 sec)

session 1查看錶資料發現color列被更新為black

 

commit

session 1先提交

 

 

等session 1提交後,session 2才update成功

 

select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | white |

|  2 | white |

|  3 | white |

|  4 | white |

+----+-------+

4 rows in set (0.00 sec)

檢視update成功後的資料情況

 

commit

session 2提交

 select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | white |

|  2 | white |

|  3 | white |

|  4 | white |

+----+-------+

4 rows in set (0.00 sec)

select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | white |

|  2 | white |

|  3 | white |

|  4 | white |

+----+-------+

4 rows in set (0.00 sec)

半一致讀semi-consistent read

半一致讀發生條件

  • RC隔離級別
  • RR隔離級別,且innodb_locks_unsafe_for_binlog=true

innodb_locks_unsafe_for_binlog

  • innodb_locks_unsafe_for_binlog預設為off。
     
  • 如果設定為1,會禁用gap鎖,但對於外來鍵衝突檢測(foreign-key constraint checking)或者重複鍵檢測(duplicate-key checking)還是會用到gap鎖。
     
  • 啟用innodb_locks_unsafe_for_binlog產生的影響等同於將隔離級別設定為RC,不同之處是:
    • innodb_locks_unsafe_for_binlog是全域性引數,影響所有session;但隔離級別可以是全域性也可以是會話級別。
    • innodb_locks_unsafe_for_binlog只能在資料庫啟動的時候設定;但隔離級別可以隨時更改。
       
      基於上述原因,RC相比於innodb_locks_unsafe_for_binlog會更好更靈活。

 
啟用innodb_locks_unsafe_for_binlog還有以下作用:

  • For UPDATE or DELETE statements, InnoDB holds locks only for rows that it updates or deletes. Record locks for nonmatching rows are released after MySQL has evaluated the WHERE condition. This greatly reduces the probability of deadlocks, but they can still happen.
    對於update或者delete語句,InnoDB只會持有匹配條件的記錄的鎖。在MySQL Server過濾where條件,發現不滿足後,會把不滿足條件的記錄釋放鎖。這可以大幅降低死鎖發生的概率。
     
  • For UPDATE statements, if a row is already locked, InnoDB performs a “semi-consistent” read, returning the latest committed version to MySQL so that MySQL can determine whether the row matches the WHERE condition of the UPDATE. If the row matches (must be updated), MySQL reads the row again and this time InnoDB either locks it or waits for a lock on it.
    簡單來說,semi-consistent read是read committed與consistent read兩者的結合。一個update語句,如果讀到一行已經加鎖的記錄,此時InnoDB返回記錄最近提交的版本,由MySQL上層判斷此版本是否滿足update的where條件。若滿足(需要更新),則MySQL會重新發起一次讀操作,此時會讀取行的最新版本(並加鎖)。

 
Consider the following example, beginning with this table:
來看下面這個例子

 
CREATE TABLE t (a INT NOT NULL, b INT) ENGINE = InnoDB; 
INSERT INTO t VALUES (1,2),(2,3),(3,2),(4,3),(5,2); 
COMMIT;

In this case, table has no indexes, so searches and index scans use the hidden clustered index for record locking (see Section 14.11.2.1, “Clustered and Secondary Indexes”).
這個例子中,表上沒有索引,所以對於記錄鎖會用到隱藏主鍵。

 
Suppose that one client performs an UPDATE using these statements:
假設某個client開啟了一個update

 
SET autocommit = 0; 
UPDATE t SET b = 5 WHERE b = 3;

Suppose also that a second client performs an UPDATE by executing these statements following those of the first client:
假設另一個client緊接著也開啟一個update

 
SET autocommit = 0; 
UPDATE t SET b = 4 WHERE b = 2;

As InnoDB executes each UPDATE, it first acquires an exclusive lock for each row, and then determines whether to modify it. If InnoDB does not modify the row and innodb_locks_unsafe_for_binlog is enabled, it releases the lock. Otherwise, InnoDB retains the lock until the end of the transaction. This affects transaction processing as follows.
每當InnoDB發起update,會先對每一行記錄加上排它鎖,然後再決定記錄是否滿足條件。如果不匹配,且innodb_locks_unsafe_for_binlog開啟,InnoDB就會把記錄上的鎖釋放掉。否則,InnoDB會一直持有鎖知道事務結束。具體如下:
 
If innodb_locks_unsafe_for_binlog is disabled, the first UPDATE acquires x-locks and does not release any of them:
如果innodb_locks_unsafe_for_binlog沒有開啟,第一個update會一直持有x鎖

 
x-lock(1,2); retain x-lock 
x-lock(2,3); update(2,3) to (2,5); retain x-lock 
x-lock(3,2); retain x-lock 
x-lock(4,3); update(4,3) to (4,5); retain x-lock 
x-lock(5,2); retain x-lock

The second UPDATE blocks as soon as it tries to acquire any locks (because the first update has retained locks on all rows), and does not proceed until the first UPDATE commits or rolls back:
第二個update會阻塞住直到第一個update提交或者回滾

 
x-lock(1,2); block and wait for first UPDATE to commit or roll back

If innodb_locks_unsafe_for_binlog is enabled, the first UPDATE acquires x-locks and releases those for rows that it does not modify:
如果innodb_locks_unsafe_for_binlog開啟,第一個update先持有x鎖,然後會釋放不匹配的記錄上面的x鎖

 
x-lock(1,2); unlock(1,2) 
x-lock(2,3); update(2,3) to (2,5); retain x-lock 
x-lock(3,2); unlock(3,2) 
x-lock(4,3); update(4,3) to (4,5); retain x-lock 
x-lock(5,2); unlock(5,2)

For the second UPDATE, InnoDB does a “semi-consistent” read, returning the latest committed version of each row to MySQL so that MySQL can determine whether the row matches the WHERE condition of the UPDATE:
對於第二個update,InnoDB會開啟半一致讀,此時InnoDB返回記錄最近提交的版本,由MySQL上層判斷此版本是否滿足update的where條件。

 
x-lock(1,2); update(1,2) to (1,4); retain x-lock 
x-lock(2,3); unlock(2,3) 
x-lock(3,2); update(3,2) to (3,4); retain x-lock 
x-lock(4,3); unlock(4,3) 
x-lock(5,2); update(5,2) to (5,4); retain x-lock

一開始的例子

RC隔離級別

session 1

session 1執行update dots set color = 'black' where color = 'white';
由於color列無索引,因此只能走聚簇索引,進行全部掃描。加鎖如下:

注:如果一個條件無法通過索引快速過濾,那麼儲存引擎層面就會將所有記錄加鎖後返回,然後由MySQL Server層進行過濾。因此也就把所有的記錄,都鎖上了。

 
但在實際中,MySQL做了優化,如同前面作用1所提到的。在MySQL Server過濾條件,發現不滿足後,會呼叫unlock_row方法,把不滿足條件的記錄放鎖 (違背了2PL的約束)。這樣做,保證了最後只會持有滿足條件記錄上的鎖,但是每條記錄的加鎖操作還是不能省略的。
實際加鎖如下:

session 2

session 2執行update dots set color = 'white' where color = 'black';
session 2嘗試加鎖的時候,發現行上已經存在鎖,InnoDB會開啟semi-consistent read,返回最新的committed版本(1,black),(2,white),(3,black),(4,white)。MySQL會重新發起一次讀操作,此時會讀取行的最新版本(並加鎖)。如同前面作用2所提到的。
加鎖如下:

MySQL優化後實際加鎖如下:

整個過程現象如前面表格所示。

RR隔離級別

session 1

session 1執行update dots set color = 'black' where color = 'white';
由於color列無索引,因此只能走聚簇索引,進行全部掃描。加鎖如下:

session 2

session 2執行update dots set color = 'white' where color = 'black';更新被阻塞。
等session 1提交commit之後,session 2update才會成功。

引申:RR隔離級別,且開啟innodb_locks_unsafe_for_binlog=ON

  • 環境準備
 
root@localhost : (none) 04:57:46> show  variables like '%iso%';
+-----------------------+-----------------+
| Variable_name        | Value          |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
| tx_isolation          | REPEATABLE-READ |
+-----------------------+-----------------+
2 rows in set (0.01 sec)
root@localhost : (none) 04:55:25> show variables like 'innodb_locks_unsafe_for_binlog';
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| innodb_locks_unsafe_for_binlog | ON    |
+--------------------------------+-------+
1 row in set (0.00 sec)
root@localhost : pxs 05:00:54> select * from dots;
+----+-------+
| id | color |
+----+-------+
|  1 | black |
|  2 | white |
|  3 | black |
|  4 | white |
+----+-------+
4 rows in set (0.00 sec)
  • 開始操作

Session 1

Session 2

begin

 

 

begin

update dots set color = 'black' where color = 'white';                Query OK, 2 rows affected (0.00 sec)

Rows matched: 2  Changed: 2  Warnings: 0

 

select lock_id,lock_trx_id,lock_mode,lock_type,lock_table,lock_index from information_schema.innodb_locks;

Empty set, 1 warning (0.00 sec)

 

 

update dots set color = 'white' where color = 'black';

Query OK, 2 rows affected (0.00 sec)

Rows matched: 2  Changed: 2  Warnings: 0

select lock_id,lock_trx_id,lock_mode,lock_type,lock_table,lock_index from information_schema.innodb_locks;

Empty set, 1 warning (0.00 sec)

 

select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | black |

|  2 | black |

|  3 | black |

|  4 | black |

+----+-------+

4 rows in set (0.00 sec)

select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | white |

|  2 | white |

|  3 | white |

|  4 | white |

+----+-------+

4 rows in set (0.00 sec)

commit

 

select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | black |

|  2 | black |

|  3 | black |

|  4 | black |

+----+-------+

4 rows in set (0.00 sec)

select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | white |

|  2 | white |

|  3 | white |

|  4 | white |

+----+-------+

4 rows in set (0.00 sec)

 

commit

select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | white |

|  2 | black |

|  3 | white |

|  4 | black |

+----+-------+

4 rows in set (0.00 sec)

select * from dots;

+----+-------+

| id | color |

+----+-------+

|  1 | white |

|  2 | black |

|  3 | white |

|  4 | black |

+----+-------+

4 rows in set (0.00 sec)

  • 注:過程現象滿足RR隔離級別,也符合設定innodb_locks_unsafe_for_binlog=ON的情況。因為前面所講的啟用innodb_locks_unsafe_for_binlog會產生作用1與作用2,所以整個加鎖與解鎖情況與RC隔離級別類似。

參考

《資料庫事務處理的藝術:事務管理與併發控制》
https://dev.mysql.com/doc/refman/5.5/en/innodb-parameters.html#sysvar_innodb_locks_unsafe_for_binlog
http://hedengcheng.com/?p=771
http://hedengcheng.com/?p=220