MySQL支持四种事务隔离级别,它们分别是:
-
READ UNCOMMITTED(未提交读):
在这个级别下,一个事务可以读取到另一个事务未提交的数据变更,也就是说一个事务还没有结束,它所做的变更就可以被其他事务看到。这会导致脏读(Dirty Read),即读取了其他事务未提交的数据。 -
READ COMMITTED(提交读):
在这个级别下,一个事务只能读取到其他事务已经提交的数据。这避免了脏读的问题。但是,它可能会遇到不可重复读(Nonrepeatable Read),即在同一个事务内,两次读取同样的数据可能会得到不同的结果,因为其他事务可能在两次读取之间修改并提交了这些数据。 -
REPEATABLE READ(可重复读):
这是MySQL的默认事务隔离级别。在这个级别下,事务在开始时创建的一致性视图会在整个事务期间保持不变。因此,可以防止不可重复读,但可能发生幻读(Phantom Read),即在同一个事务内,两次执行相同的查询,可能会因为其他事务新增了符合查询条件的行而得到不同的行数。 -
SERIALIZABLE(序列化):
这是最严格的隔离级别,事务串行执行,从而避免了脏读、不可重复读以及幻读。但是这个级别会大量使用锁,从而导致并发性能下降。
不同的隔离级别在并发性能和数据正确性之间提供了不同程度的权衡。在实际应用中,事务隔离级别的选择往往取决于事务的业务逻辑以及你能够容忍的一致性问题程度。高级别的隔离能够提供更多的数据安全性保证,但可能会牺牲一定的性能。
在不同的事务隔离级别下,可能会遇到以下几种数据一致性问题:
-
脏读(Dirty Read):这是一个事务读取到了另一个事务修改但并未提交的数据。如果另一个事务回滚,那么这个事务读到的数据就是错误的。这种问题一般发生在“Read Uncommitted”隔离级别。
-
不可重复读(Non-Repeatable Read):在同一事务内,多次读取同一数据,中间的结果却不一致,这是由于在读取数据的过程中,有另外一个事务也在访问同一数据,且对其做了修改并提交,导致不可重复读。这一般发生在“Read Uncommitted”和“Read Committed”隔离级别。
-
幻读(Phantom Read):在同一事务内,进行了两次相同的查询,但返回的结果行数不同。这是因为有另一个并发事务在这两次查询间插入了新的行。这种情况在"Read Committed"和"Repeatable Read"隔离级别中都可能发生。
多版本并发控制(MVCC)
InnoDB的MVCC是通过在每一行数据后面保存两个隐藏的列来实现的。这两个列分别保存了行的创建时系统版本号和删除时系统版本号。每一个新的事务,系统版本号都会自动递增,事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行对比。
- SELECT 只会查询版本早于当前系统版本号和行的删除版本号要么未定义,要么大于当前事务版本号
- INSERT 为新插入的每一行数据保存当前系统版本号作为行版本号
- DELETE 为删除的每一行数据保存当前系统版本号作为删除标识
- UPDATE 插入一条新记录,保存当前系统版本好作为行版本号,同时保存当前系统作为原数据行的删除标识
保存这两个系统版本号,使大多数读操作不用加锁,这样使读操作简单化,性能更好,不足之处就是每个记录需要额外的存储空间,需要进行更多的行检查,以及一些额外的维护工作。
为什么 MySQL 选择 “可重复读” 作为默认隔离级别
数据一致性和性能的平衡:
“可重复读”提供了较强的数据一致性保证(避免了脏读和不可重复读),同时比“可串行化”的性能开销小。因此,这种隔离级别在保证数据一致性的同时,能够实现较高的性能。
避免脏读和不可重复读:
对很多应用来说,避免脏读和不可重复读是基本要求。“可重复读”隔离级别可以很好地满足这一点。
幻读问题的解决
- 多版本并发控制(MVCC):InnoDB 通过维护数据的多个版本,实现高效的读写并发,保证在“可重复读”隔离级别下,每个事务读取到的是一致的视图。
- 间隙锁(Next-Key Locking):在事务执行过程中,InnoDB 会锁定索引范围内的间隙,避免其他事务在这些间隙中插入或删除数据,从而防止了幻读问题。
- 尽管“可重复读”存在幻读问题,但 MySQL 的 InnoDB 存储引擎通过多版本并发控制(MVCC)和间隙锁(Next-Key Locking)机制,部分解决了幻读问题。