MySQL事务的四个特征(ACID)以及隔离级别

本文详细介绍了MySQL的ACID特性,包括原子性、一致性、隔离性和持久性,并通过事务日志和锁机制阐述了其实现方式。接着讨论了MySQL的四种隔离级别及其可能导致的问题,如脏读、不可重复读和幻读。此外,还解释了MVCC在读已提交和可重复读隔离级别中的作用,以及为何读已提交存在不可重复读问题。最后,提到了InnoDB存储引擎中的锁机制和间隙锁在解决幻读和死锁问题上的应用。
摘要由CSDN通过智能技术生成

1.MySQL的四个特性(ACID)

InnoDB存储引擎还提供了两种事务日志:redo log(重做日志)和undo log(回滚日志)。其中redo log用于保证事务持久性;undo log则是事务原子性实现的基础。

  • 原子性 (Atomicity)

事务是一个原子操作单元,其对数据的修改要么全部执行,要么全不执行。

举例:假设有一个银行转账事务,从账户 A 转 100 元到账户 B。这个转账事务包括两个操作:从账户 A 中扣除 100 元和给账户 B 增加 100 元。这两个操作要么都成功,要么都失败,不可能只执行其中一个。

  • 一致性 (Consistency)

事务必须使数据库从一个一致性状态转变到另一个一致性状态。

举例:继续使用上面的银行转账例子,转账前后,两个账户的总金额是不变的。即转账前 A 账户有 500 元,B 账户有 300 元,转账后 A 账户变为 400 元,B 账户变为 400 元,但两者的总和仍然是 800 元。

  • 隔离性 (Isolation)

在事务进行和完成期间,事务的中间状态对其他事务是不可见的。

举例:考虑两个并发的转账事务,一个是账户 A 转给账户 B 100 元,另一个是账户 C 转给账户 D 50 元。由于隔离性,这两个事务不会互相干扰。即使它们同时发生,一个事务也不会看到另一个事务的中间状态。

  • 持久性 (Durability)

一旦事务完成,则其结果就是永久性的。

举例:继续上面的银行转账例子,假设事务成功地从账户 A 转了 100 元到账户 B,那么即使系统崩溃或重启,这个转账的结果仍然是有效的,不会丢失。

2.mysql底层如何实现ACID

  • 原子性和持久性:这两个特性通常是通过事务日志(Redo log 和 Undo log)来实现的。当事务提交时,对应的 Redo log 会被写入磁盘以保证持久性,而 Undo log 则用于回滚和保证原子性。
  • 隔离性:在底层,MySQL通过锁机制和多版本并发控制(MVCC)来实现不同隔离级别的事务隔离。
  • 一致性:依赖于事务的其它三个属性(原子性、隔离性、持久性)共同保证的,只要事务是原子的、隔离的、持久的,它自然就能保证数据库状态的一致性转换。

总的来说,MySQL 通过使用事务日志、数据库约束、锁机制和多版本并发控制等技术来实现事务的 ACID 特性。这些机制确保了事务的原子性、一致性、隔离性和持久性。

3.MySQL的四种隔离级别

请添加图片描述

  • Read Uncommitted(读未提交)
    在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(DirtyRead)。会有脏读、不可重复读和幻读的问题

增(INSERT)/删(DELETE)/改(UPDATE):会加行锁,防止其他事务同时操作相同的记录。
查(SELECT):不会加任何锁,读取到其他事务尚未提交的数据。可能产生脏读(Dirty Read)问题。

  • Read Committed(读已提交)
    读取已提交数据,不可重复读,它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。会有不可重复读和幻读的问题。

增(INSERT)/删(DELETE)/改(UPDATE):会加行锁,防止其他事务同时操作相同的记录。直到当前事务完成(提交或回滚)才会释放。
查(SELECT):不会加任何锁。只能读取到已经提交的数据,避免了脏读的问题,但可能产生不可重复读(Non-repeatable Read)和幻读(Phantom Read)。

  • Repeatable Read(可重复读)
    读已提交数据,可重复读。这是MySQL的默认事务隔离级别,会有幻读问题。

增(INSERT)/删(DELETE)/改(UPDATE):会加行锁,防止其他事务同时操作相同的记录。直到当前事务完成(提交或回滚)才会释放。
查(SELECT):一般情况下不加锁,但是如果使用了锁定读(例如SELECT…FOR SHARE 或 SELECT…FOR
UPDATE),则会加锁。该级别通过多版本并发控制(MVCC)机制解决了“不可重复读”问题,但可能产生幻读(Phantom Read)。

  • Serializable(可串行化)
    事务顺序执行,没有并行,完全杜绝幻读。这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争

增(INSERT)/删(DELETE)/改(UPDATE):会加行锁,防止其他事务同时操作相同的记录。直到当前事务完成(提交或回滚)才会释放。
查(SELECT):会加共享锁,所有的读操作都必须等待写操作执行完毕,从而保证完全的隔离,防止了脏读,不可重复读和幻读。

4.事务并发遇到的几个问题

1.脏写(两个修改的事务)
有多种情况,在事务A修改数据提交前,事务B修改了数据,发现没生效,事务B就是脏写了。(以上4种隔离级别下,都不存在脏写情况,原因下面有)
列举以下两种情况:

  • 如下,事务B在事务A修改提交事务前,事务B先修改提交事务,然后事务A提交事务,事务A覆盖了事务B修改的数据。对事务B来说,发现自己修改的数据没生效,事务B就是脏写了。

请添加图片描述

  • 或者事务B先提交事务,然后事务A回滚,也会造成事务B脏写

注:
文上提到的4种隔离级别下,都不存在脏写情况。因为在这些隔离级别下,当两个事务A和B尝试去更新同一条数据时,假定A先更新数据,会对更新的数据行记录加上排他锁(也叫写锁,悲观锁),除非事务A提交或终止从而释放排他锁,否则事务B都是无法更新数据的。(设计数据密集型应用只是说读提交隔离级别一定可以杜绝脏写问题,并未提到读未提交隔离级别,经过实践,读未提交下事务B的更新操作也是需要等待事务A的排他锁释放,才得以执行)

2.脏读(一个读事务、一个修改事务)

  • 事务A修改数据后,但是还没有提交事务,此时被事务B读取到了数据,然后事务A回滚了,事务B就是脏读了,读到了无效的数据,脏数据。

请添加图片描述
示例:
事务A把小王的钱转了100给小明,但是还没有提交事务,小明登录自己的银行APP查看自己的余额,发现多了100。然后正准备提现,此时事务A回滚了。小明提现失败,再次查询发现没有了100大洋。小明很崩溃。

脏读演示:
首先把隔离级别改为读未提交(要重启命令提示符)

set global transaction_isolation ='read-uncommitted';

查看隔离级别

show global variables like '%isolation%';

如下所示,事务回滚,读的就是脏数据
在这里插入图片描述

3.不可重复读(一个读事务、一个修改事务)
一个事务中多次相同的查询,查到了不同的结果,就是不可重复读。

例子:事务A查询某值为5,事务B修改为8并提交事务,事务A再次查询时值变成了8,即为不可重复读。

4.幻读(一个读事务、一个新增事务)
在一个事务中使用相同的 SQL 两次读取,数据量不一致。

例子:事务A第一次查询到5条数据,事务B新增了3条数据并提交,事务A再次查询就变成了8条数据,这时事务A出现了幻读。

5、MVCC

RC、RR隔离级别都是基于MVCC实现的

推荐参考:深入理解MySQL中的MVCC(多版本并发控制)

那么为什么RC不能解决不可重复读问题呢?

答:读已提交事务每次查询都会创建一个read view,而可重复读每个事务只会创建一次read view。
这就导致读已提交每次都是读的最新数据会有不可重复读问题,而可重复读每次都是读的同一个版本数据。

6、扩展

1、update … where age = ? 锁的是什么?
如果是 MyISAM 存储引擎锁的是表
如果是InnoDB存储引擎,age是索引,则锁的是行数据(行锁是锁的索引);如果age不是索引,则锁的是全表。

2、为什么age不是索引会锁住全表?
这是因为在没有索引的情况下,InnoDB 存储引擎无法只锁定特定的行(因为没有索引来快速定位到特定的行),无法知道对应行数据在磁盘中的位置。

当前读:读取最新数据(update、insert、delete、select for update)
快照读:通过版本链读取undo log中数据(普通select)

3、可重复读隔离级别 中的 快照读有幻读情况吗?
没有,因为快照读通过mvcc实现的是从版本链中读取数据,不会有幻读情况。
但是当前读会有,当前读需要解决幻读可以通过间隙锁实现。

4、版本链是什么?
版本链是由undo log 和回滚指针(roll_pointer)连接起来的。

5、RR隔离级别是如何解决幻读的?
通过间隙锁解决了幻读,但是很多企业用的隔离级别是读已提交(RC),因为读已提交没有间隙锁,大大减小了锁开销,而且幻读可以看做是一种正常现象而不是问题。如果要解决幻读可以

6、如何解决RR隔离级别下间隙锁引起的死锁问题?

例:RR级别中一个事务里面先 select for update where age = 10,但是没有age=10这行数据,只有age=5 和
age = 15,那么会锁住(5,15)这个区间,也就是间隙锁。这时候再同一个事务里面 update where age = 10会加锁失败,就会死锁。
因为间隙锁不互斥,但是间隙锁和插入语句互斥。如果是两个查询是没问题的。

  1. 降低隔离级别到rc
  2. 采用分布式锁

首先感谢以下博主资源,附上链接:
http://www.zhai14.com/blog/strenghen-comprehension-on-dirty-read-and-phantom.html
https://zhuanlan.zhihu.com/p/150107974
https://blog.csdn.net/qq_38238296/article/details/88363017
https://www.cnblogs.com/xrq730/p/5087378.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值