InnoDB的行锁,原来为你做了这么多

从事务的隔离级别谈起众所周知 , 事务有四大特性 , 简称ACID:原子性、一致性、隔离性、持久性 。
对于隔离性 , 简单来说就是多个事务之间是彼此隔离的 , 互不影响 。但想要做到完全的互不影响是很难的 , 因为数据的强一致性 , 很多时候需要牺牲性能去达成 。比如如果我们能接受事务的串行执行 , 那一定是互不影响的 。然而现实是 , MySQL作为一个数据库 , 必然是要支持一定程度的并行执行的 , 也就是多个事务同时去执行 。

?
凡并行程序 , 往往是在性能和数据一致性上做取舍 。较好的解决方案要么是最终一致 , 要么是尽量缩小串行执行的范围 。
?
如果多个事务同时并行执行 , 在没有隔离的情况 , 可能会发生脏读、不可重复读、幻读的问题 。
案例数据(demo表):
id(主键) c(普通索引) d(无索引) 5 5 5 10 10 10 15 15 15 20 20 20 25 25 25
「脏读」
一个事务读取了另一个事务未提交的数据 。
InnoDB的行锁,原来为你做了这么多

文章插图
 
脏读
「不可重复读」
一个事务读取同一行数据 , 多次读取结果不同 。
InnoDB的行锁,原来为你做了这么多

文章插图
 
不可重复读
「幻读」
一个事务读取到了别的事务插入的数据 。
InnoDB的行锁,原来为你做了这么多

文章插图
 
幻读
但InnoDB因为使用了MVCC , 读取的是“快照”版本 , 有一些不同 , 但如果不上锁 , 同样可能会有幻读问题 。
InnoDB的行锁,原来为你做了这么多

文章插图
 
InnoDB的幻读
事务用了四种不同的隔离级别用来解决这些问题 。
  • Read uncommitted(未提交读)
  • Read Committed(已提交读 , 简称RC)
  • Repeatable Reads(可重复读 , 简称RR)
  • Serializable(串行化)
隔离级别越高 , 解决的问题越多 , 但并发性能也会越差 。它们之间的关系如下表:
隔离级别 脏读 不可重复读 幻读 Read uncommitted 是 是 是 Read Committed 是 是 Repeatable Reads 是 Serializable
?
但InnoDB有些许不同 , InnoDB默认的隔离级别是RR , 但是通过MVCC和间隙锁来一定程度上的解决了幻读的问题 。这也是我们今天这篇文章后面会详细介绍的 。
?
无锁思想:MVCCMVCC即“多版本并发控制” , 但是它在很多情况下避免了加锁操作 , 因此开销更低 。
主流的关系型数据库都实现了MVCC , 但实现机制各有不同 。实际上MVCC也没有一个统一的标准 。但大都实现了非阻塞的读操作 , 写操作也只是锁定必要的行 。本文以下内容所说的MVCC都指的是InnoDB实现的MVCC 。
在Mysql的InnoDB引擎 , 是通过给每行记录后面保存两个隐藏的列来实现的 。一个是保存行的创建时间 , 另一个保存了行的过期时间(或删除时间) 。
?
实际上存储的并不是实际的一个时间戳 , 而是“系统版本号” 。
?
每次开启一个事务 , 系统版本号都会递增 。事务开始时 , 系统版本号会作为事务的版本号 , 用来和查询到的行的版本号进行比较 。
MVCC只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作 , 其它两个隔离级别不能工作 。因为READ UNCOMMITTED总是读取最新的数据行 , 而不是符合当前事务版本的数据行 。而SERIALIZABLE则会对所有读取的行都加锁 。
在MySQL中 , 正常的SELECT语句 , 后面不加FOR UPDATE和LOCK IN SHARE MODE的 , 就是用的MVCC去读 。
?
MVCC和我们在应用层面去实现的“乐观锁”有一样的思想:用版本号 , 在尽量无锁的情况下实现一定程度的一致性 。
?
InnoDB行锁的概念InnoDB的行锁(也称为临键锁) Next-Key Locks , 「是MySQL对外暴露的锁的基本单位 , 它会智能选择记录锁或间隙锁 , 锁住一行或多行或一个间隙」 。而记录锁又分为共享锁和排他锁 , 间隙锁的概念下面有一个插入意向锁 。这些锁的关系大概是这样:


推荐阅读