Mysql事务隔离性与相关锁的总结

Posted hongdada

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mysql事务隔离性与相关锁的总结相关的知识,希望对你有一定的参考价值。

事务隔离级别

隔离级别 脏读 不可重复读 幻读
读未提交 可以出现 可以出现 可以出现
读提交 不允许出现 可以出现 可以出现
可重复读 不允许出现 不允许出现 可以出现
序列化 不允许出现 不允许出现 不允许出现

注意点

上述隔离级别都是这么定义的,但是InnoDB和Falcon存储引擎已经通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了幻读问题。

已提交读 ReadCommitted 读取 不加锁 写入 加行锁(如果按索引过滤,那么只对筛选数据进行加行锁,但是如果不是按索引进行过滤,就对所有记录加行锁(但是mysql对此进行了优化,会将不满足条件的记录释放锁))
可重复读 RepeatableRead

锁的类型

InnoDB 实现了两种类型的行级锁:

  • 共享锁

    (也称为 S 锁):允许事务读取一行数据。

    可以使用 SQL 语句 select * from tableName where … lock in share mode; 手动加 S 锁。

  • 独占锁

    (也称为 X 锁):允许事务删除或更新一行数据。

    可以使用 SQL 语句 select * from tableName where … for update; 手动加 X 锁。

S 锁和 S 锁是兼容的,X 锁和其它锁都不兼容,举个例子,事务 T1 获取了一个行 r1 的 S 锁,另外事务 T2 可以立即获得行 r1 的 S 锁,此时 T1 和 T2 共同获得行 r1 的 S 锁,此种情况称为锁兼容,但是另外一个事务 T2 此时如果想获得行 r1 的 X 锁,则必须等待 T1 对行 r 锁的释放,此种情况也成为锁冲突

为了实现多粒度的锁机制,InnoDB 还有两种内部使用的意向锁,由 InnoDB 自动添加,且都是表级别的锁。

  • 意向共享锁(IS):事务即将给表中的各个行设置共享锁,事务给数据行加 S 锁前必须获得该表的 IS 锁。
  • 意向排他锁(IX):事务即将给表中的各个行设置排他锁,事务给数据行加 X 锁前必须获得该表 IX 锁。

意向锁的主要目的是为了使得行锁表锁共存。表 2 列出了行级锁和表级意向锁的兼容性。

行级锁和表级意向锁的兼容性

锁类型 X IX S IS
X 冲突 冲突 冲突 冲突
IX 冲突 兼容 冲突 兼容
S 冲突 冲突 兼容 兼容
IS 冲突 兼容 兼容 兼容

行锁的算法

InnoDB 存储引擎使用三种行锁的算法用来满足相关事务隔离级别的要求。

  • Record Locks

    该锁为索引记录上的锁,如果表中没有定义索引,InnoDB 会默认为该表创建一个隐藏的聚簇索引,并使用该索引锁定记录。

  • Gap Locks

    该锁会锁定一个范围,但是不括记录本身。可以通过修改隔离级别为 READ COMMITTED 或者配置 innodb_locks_unsafe_for_binlog 参数为 ON

  • Next-key Locks

    该锁就是 Record Locks 和 Gap Locks 的组合,即锁定一个范围并且锁定该记录本身。InnoDB 使用 Next-key Locks 解决幻读问题。需要注意的是,如果索引有唯一属性,则 InnnoDB 会自动将 Next-key Locks 降级为 Record Locks。举个例子,如果一个索引有 1, 3, 5 三个值,则该索引锁定的区间为 (-∞,1], (1,3], (3,5], (5,+ ∞)

一致性非锁定读和多版本并发控制

一致性非锁定读(consistent nonlocking read)是指InnoDB通过行多版本控制(Multi Version Concurrency Control, MVCC)的方法来读取当前执行时间数据库中行的数据。

即如果读取的行正在执行变更操作,这时读取不会等待行锁的释放,而是会读取行的一个快照数据。快照是指该行的一个历史数据,通过undo操作来完成。这种方式极大提高了数据库的并发性,这也是InnoDB的默认设置。

快照是当前行的一个历史版本,但可能存在多个版本,行数据存在多个快照数据,这种技术成为行多版本技术,由此带来的并发控制,称为多版本并发控制(MVCC)。InnoDB在READ COMMITED 和 REPEATABLE READ隔离级别时,会使用非锁定的一致性读,但是在这两种隔离级别使用的快找数据定义却不同:

  • READ COMMITED: 总是读取最新一份快照
  • REPEATABLE READ: 总是读取事务开始时的行数据版本

我们执行一个示例:

一致性非锁定读
时间 会话A 会话B
1 BEGIN
2 select * from z where a = 3;
3 BEGIN
4 update z set b=2 where a=3;
5 select * from z where a = 3;
6 COMMIT;
7 select * from z where a = 3;
8 COMMIT;

在这个例子中我们可以清晰的看到0、1、2三种隔离级别的区别:

#在事务开始前我们可以分别调整为0、1、2三种隔离级别,来查看不同的输出
mysql> set session transaction isolation level READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation   |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set (0.00 sec)

# A会话:T1事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from z where a = 3;
+---+------+
| a | b    |
+---+------+
| 3 |    1 |
+---+------+
1 row in set (0.00 sec)

# B会话:T2事务
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update z set b=2 where a=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

# A会话:T1事务,如果此时隔离级别是READ-UNCOMMITTED,因为此刻事务2可能会回滚,所以出现了脏读
mysql> select * from z where a=3;
+---+------+
| a | b    |
+---+------+
| 3 |    2 |
+---+------+
1 row in set (0.00 sec)

# A会话:T1事务,如果此时隔离级别是大于READ-UNCOMMITTED的更高级别
mysql> select * from z where a=3;
+---+------+
| a | b    |
+---+------+
| 3 |    1 |
+---+------+
1 row in set (0.00 sec)

# B会话:T2事务
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

# A会话:T1事务,如果此时隔离级别是READ-COMMITTED,因为数据和事务开始时读取的出现了不一致,因此称为不可重复读,能够读到其他事务的结果,违反了事务的隔离性
mysql> select * from z where a=3;
+---+------+
| a | b    |
+---+------+
| 3 |    2 |
+---+------+
1 row in set (0.00 sec)

# A会话:T1事务,如果此时隔离级别是大于READ-COMMITTED的更高级别
mysql> select * from z where a=3;
+---+------+
| a | b    |
+---+------+
| 3 |    1 |
+---+------+
1 row in set (0.00 sec)

# A会话:T1事务
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

一致性锁定读和SERIALIZABLE隔离

在默认的REPEATABLE READ隔离级别时,InnoDB使用的是一致性非锁定读。但有时我们也需要显示的指定使用一致性锁定读来保证读取操作时对数据进行加锁达到一致性。这要求数据库支持锁定读加锁语句:

  • select ... for update: 读取时对行记录加X锁
  • select ... lock in share mode:读取时对行记录加一个S锁

这两种锁必须在一个事务中,当事务提交后锁也就释放了,因此务必加上BEGIN, START TRANSACTION或者SET AUTOCOMMIT=0。

我们在前面隔离级别时也说过SERIALIZABLE隔离级别会对读操作自动加上LOCK IN SHARE MODE指令来加上一个共享锁,因此不再支持一致性的非锁定读。这也是隔离级别3的一大特性。

参考

MySQL 事务隔离级别和锁

MySQL的锁和事务隔离级别

以上是关于Mysql事务隔离性与相关锁的总结的主要内容,如果未能解决你的问题,请参考以下文章

mysql事务隔离级别与锁的关系

RDB | MySQL的事务隔离级别和锁的机制

Mysql数据库事务的隔离级别和锁的实现原理分析

第36讲 谈谈MySQL支持的事务隔离级别,以及悲观锁和乐观锁的原理和应用场景

mysql/mariadb知识点总结 之 事务总结

mysql/mariadb知识点总结 之 事务总结