RC与RR隔离级别下MySQL不同的加锁解锁方式

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)

查看是否存在锁冲突 

root@localhost : 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)

此时再查看,确实存在锁冲突

root@localhost : 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


猜你喜欢

转载自blog.csdn.net/wukong_666/article/details/80239758