mysql事务的4种隔离级别及InnoDB锁机制

434 阅读10分钟

1.前言

本文是一篇个人总结性文章,参考了各位大佬的博客,并根据自己学习路线总结得到。 数据库中事务的四大特性:原子性、隔离性、持久性、一致性;那么在mysql中是如何实现这四大特性的?

1.1 mysql如何保证原子性?

原子性:事务所有操作要么全部提交成功,要么全部失败回滚。
实现:InnoDB中是通过undo log(回滚日志)实现。当事务回滚时能撤销所有已经成功执行的SQL语句,他需要记录你要回滚的相应日志信息。

(1)当你delete一条数据的时候,就需要记录这条数据的信息,回滚的时候,insert这条旧数据

(2)当你update一条数据的时候,就需要记录之前的旧值,回滚的时候,根据旧值执行update操作

(3)当年insert一条数据的时候,就需要这条记录的主键,回滚的时候,根据主键执行delete操作

undo log记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。

1.2 mysql如何保证持久性?

持久性:一旦事务提交,其所做的修改将永远保存到数据库中,即使发生崩溃,事务执行的结果也不能丢失。
实现:InnoDB中是通过redo log

问题:Mysql是先把磁盘上的数据加载到内存中,在内存中对数据进行修改,再刷回磁盘上。如果此时突然宕机,内存中的数据就会丢失。
如何解决:采用redo log解决上面的问题。当做数据修改的时候,不仅在内存中操作,还会在redo log中记录这次操作。当事务提交的时候,会将redo log日志进行刷盘(redo log一部分在内存中,一部分在磁盘上)。当数据库宕机重启的时候,会将redo log中的内容恢复到数据库中,再根据undo log和binlog内容决定回滚数据还是提交数据。
采用redo log的好处:redo log体积小,毕竟只记录了哪一页修改了啥,因此体积小,刷盘快。 redo log是一直往末尾进行追加,属于顺序IO。效率显然比随机IO来的快。

1.3 mysql如何保证隔离性?

隔离性:一个事务所做的修改在最终提交之前 对其他事务是不可见的

问题:在并发环境下,事务的隔离性很难保证,会出现很多并发一致性问题。

解决:对于并发一致性问题解决办法一般有:封锁和数据库管理系统提供的事务隔离级别以及多版本并发控制

封锁:行级锁和表级锁

事务隔离级别:见下文

多版本并发控制MVCC:见下文

1.4 mysql如何保证一致性?

从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性。例如,原子性无法保证,显然一致性也无法保证。

但是,如果你在事务里故意写出违反约束的代码,一致性还是无法保证的。例如,你在转账的例子中,你的代码里故意不给B账户加钱,那一致性还是无法保证。因此,还必须从应用层角度考虑。 从应用层面,通过代码判断数据库数据是否有效,然后决定回滚还是提交数据

2.四种隔离级别

低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。 参考:www.iteye.com/blog/xm-kin…

2.1 Read uncommitted(读取未提交内容)

(1)所有事务都可以看到其他未提交事务的执行结果

(2)本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少

(3)该级别引发的问题是——脏读(Dirty Read):读取到了未提交的数据

2.2 Read commited(读取提交内容RC)

针对当前读,RC隔离级别保证了对读取到的记录加锁(记录锁),存在幻读现象。

(1)这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)

(2)它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变

(3)这种隔离级别出现的问题是——不可重复读(Nonrepeatable Read):不可重复读意味着我们在同一个事务中执行完全相同的select语句时可能看到不一样的结果。

 |——>导致这种情况的原因可能有:(1)有一个交叉的事务有新的commit,导致了数据的改变;(2)一个数据库被多个实例操作时,同一事务的其他实例在该实例处理其间可能会有新的commit

2.3 Repeatable Read(可重读RR)

InnoDB存储引擎默认的隔离级别是可重复读,即RR。针对当前读,RR隔离级别保证对读取到的记录加锁(记录锁),同时保证对读取的范围加锁,新的满足查询条件的记录不能够插入(间隙锁),不存在幻读现象。

(1)这是MySQL的默认事务隔离级别

(2)它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行

(3)此级别可能出现的问题——幻读(Phantom Read):当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行

(4)InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题

2.3.1 重要:RR如何解决幻读现象

通过MVCC+Next key lock:

很明显可重复读的隔离级别没有办法彻底的解决幻读的问题,如果需要解决幻读的话也有两个办法:

  • 使用串行化读的隔离级别
  • MVCC+next-key locks:next-key locks由record locks(索引加锁) 和 gap locks(间隙锁,每次锁住的不光是需要使用的数据,还会锁住这些数据附近的数据)

2.4 Serializable(可串行化)

从MVCC并发控制退化为基于锁的并发控制。不区别快照读和当前读,所有的读操作都是当前读,读加读锁(S锁),写加写锁(X锁)。在该隔离级别下,读写冲突,因此并发性能急剧下降,在MySQL/InnoDB中不建议使用。

(1)这是最高的隔离级别

(2)它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。

(3)在这个级别,可能导致大量的超时现象和锁竞争

2.5 小结

X表示此隔离级别已解决的情况

3.分布式事务

juejin.cn/post/684490…

4.脏读

见RC

5.phantom problem幻读

幻读指的是在同一事务下,连续执行两次同样的SQL语句可能会导致不同的结果,第二次的SQL可能会返回之前不存在的行。

5.1 如何解决幻读问题

MVCC+Next-key locks

  • 什么是MVCC 读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的提高了系统的并发性能,在现阶段,几乎所有的RDBMS,都支持MVCC。其实,MVCC就一句话总结:同一份数据临时保存多个版本的一种方式,进而实现并发控制;

多版本并发控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,无需使用 MVCC。可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。

Mysql的大多数事务型存储引擎实现都不是简单的行级锁。基于提升并发性考虑,一般都同时实现了多版本并发控制(MVCC),包括Oracle、PostgreSQL。不过实现各不相同。

MVCC的实现是通过保存数据在某一个时间点快照来实现的。也就是说不管实现时间多长,每个事物看到的数据都是一致的。

分为乐观(optimistic)并发控制和悲观(pressimistic)并发控制

6.几个特殊的索引

6.1 辅助索引

6.2 聚集索引

也叫做聚簇索引。在InnoDB中,数据的组织方式就是聚簇索引:完整的记录,储存在主键索引中,通过主键索引,就可以获取记录中所有的列。

6.3 唯一属性(主键索引,唯一索引)

  • 主键索引:

主键索引不可以为空 主键索引可以做外键 一张表中只能有一个主键索引

  • 普通索引:

用来加速数据访问速度而建立的索引。多建立在经常出现在查询条件的字段和经常用于排序的字段。 被索引的数据列允许包含重复的值

  • 唯一索引:

被索引的数据列不允许包含重复的值

6.4 复合索引

www.jianshu.com/p/3cd3cec2e…

7.mysql锁机制

7.1 InnoDB存储引擎实现了两种标准的行级锁:

参考:mp.weixin.qq.com/s/T6g7tbRuz…

  • 共享锁 S Lock ,允许事务读一行数据

  • 排它锁 X Lock ,允许事务删除或更新一条数据

如果一个事务T1已经获得了r的共享锁,那么另外的事务T2可以立即获得行r的共享锁,因为读取并没有改变r的数据,成这种情况为锁兼容(Lock Compatible)。但若有其他的事务T3箱获得行r的排它锁,则其必须等待T1、T2释放行r上的共享锁 —— 这种情况称为锁不兼容。

7.2 InnoDB有3种行锁的算法

  • 1.记录锁 record lock 单个行记录上的锁,就是字面意思的行锁

  • 2.间隙锁 Gap lock 锁定某一个范围内的索引,但不包括记录本身
  • 3.next-key lock:gap lock+record lock 锁定一个范围,并且锁定记录本身

7.3 InnoDB行锁的特点

  • 1.当查询的索引含有唯一(unique)属性时(主键索引,唯一索引)InnoDB存储引擎会对Next-Key Lock优化,将其降级为Record Lock,即仅锁住索引本身,不是范围。

例子:

用户也可以通过以下两种方式关闭gap lock对辅助索引的下一个值的锁定。

1.配置参数 innodb_locks_unsafe_for_binlog = 1

2.隔离级别设为 READ COMMITTED

  • 2.复合主键下,如果加锁时不带上所有主键,InnoDB会使用Next-Key Locking算法,如果带上所有主键,才会当作唯一索引处理,降级为Record Lock,只锁当前记录。否则依然为Next-key lock

  • 3.当使用多列唯一索引时,加锁需要明确要锁定的行(即加锁时使用索引的所有列),InnoDB才会认为该条记录为唯一值,锁才会降级为Record Lock。否则会使用Next-Key Lock算法,锁住范围内的数据。

  • 4.小结
    • 当唯一索引是由多个列组成,而query仅查询多个列中的其中一个,则依然使用 Next-key lock。
    • 强制关闭Gap Lock,仅使用记录锁从而避免阻塞(有幻读的风险):配置参数 innodb_locks_unsafe_for_binlog = 1 或者隔离级别设为 READ COMMITTED
    • 在使用Mysql中的锁时要谨慎使用,尤其时更新/删除数据时,尽量使用主键更新,如果在复合主键表下更新时,一定通过所有主键去更新,避免锁范围变大带来的死锁等问题。

参考:segmentfault.com/a/119000002…