MySql事务详解
MySQL事务
- 四大特性
- 原子性:事务中包含的各操作要么都做,要么都不做
- 一致性:事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。
- 隔离性:一个事务的执行不能其它事务干扰
- 持久性:事务一旦提交,它对数据库中的数据的改变就应该是永久性的
- SQL 标准的事务隔离级别包括
- 读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read,默认的隔离级别)和串行化(serializable )
- 隔离级别操作
- 查看当前会话隔离级别
- select @@tx_isolation;
- 查看系统当前隔离级别
- select @@global.tx_isolation;
- 设置当前会话隔离级别
- set session transaction isolatin level repeatable read;
- 设置系统当前隔离级别
- set global transaction isolation level repeatable read;
- 查看当前会话隔离级别
- 事务
- 读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。
- 读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
- 可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
- 串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
分析
事务A | 事务B |
---|---|
启动事务查询得到值1 | 启动事务 |
查询得到值1 | |
将1改为2 | |
查询得到值v1 | |
提交事务 | |
查询得到值v2 | |
提交事务A | |
查询得到值v3 |
若隔离级别是“读未提交”, 则 V1 的值就是 2。这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此,V2、V3 也都是 2。
若隔离级别是“读提交”,则 V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A 看到。所以, V3 的值也是 2。
若隔离级别是“可重复读”,则 V1、V2 是 1,V3 是 2。之所以 V2 还是 1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。
若隔离级别是“串行化”,则在事务 B 执行“将 1 改成 2”的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2。
“快照”在 MVCC 里是怎么工作的?参考
InnoDB 里面每个事务有一个唯一的事务 ID,叫作 transaction id。它是在事务开始的时候向 InnoDB 的事务系统申请的,是按申请顺序严格递增的。
而每行数据也都是有多个版本的。每次事务更新数据的时候,都会生成一个新的数据版本,并且把 transaction id 赋值给这个数据版本的事务 ID,记为 row trx_id。同时,旧的数据版本要保留,并且在新的数据版本中,能够有信息可以直接拿到它。MVCC
每一个版本的数据行都具有一个唯一的时间戳,当有读事务请求时,数据库程序会直接从多个版本的数据项中具有最大时间戳的返回。
更新操作就稍微有些复杂了,事务会先读取最新版本的数据计算出数据更新后的结果,然后创建一个新版本的数据,新数据的时间戳是目前数据行的最大版本 +1:事务的启动方式
- 显式启动事务语句, begin 或 start transaction。配套的提交语句是 commit,回滚语句是 rollback。
脏读
- 当数据库中一个事务A正在修改一个数据但是还未提交或者回滚,另一个事务B 来读取了修改后的内容并且使用了,之后事务A提交了,此时就引起了脏读。此情况仅会发生在: 读未提交的的隔离级别
不可重复读
- 在一个事务A中多次操作数据,在事务操作过程中(未最终提交),事务B也才做了处理,并且该值发生了改变,这时候就会导致A在事务操作的时候,发现数据与第一次不一样了。 就是不可重复读。此情况仅会发生在:读未提交、读提交的隔离级别.
幻读
- 一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为幻读。此情况会回发生在:读未提交、读提交、可重复读的隔离级别
事务的两阶段提交
指的就是在事务提交的时候,分成 prepare 和 commit 两个阶段。redo log 先 prepare 完成,再写 binlog,最后才进入 redo log commit 阶段。
为什么事务的隔离级别是RR
在MySQL的早期版本(大概是5.1)中,binlog的默认格式是语句格式。这时候如果启用了RC的隔离级别,binlog记录的顺序可能与实际不一致。所以系统做了一个判断,如果隔离级别为RC,则binlog格式必须要是Mix或者row。
性能,问题是有可能出现幻读,mysql联合使用next-key locking解决幻读。
RR为了实现真正的“可重复读”,引入了间隙锁(Gap lock),不仅锁住了行,而且锁住了行与行之间的间隙,避免出现幻读。
Redo Log 保证了事务的持久性, Undo Log 保证了事务的原子性,而写入 Commit 记录了事务的提交点
- 脏写(Dirty Write),即有两个事务 T1 和 T2 , T1 更改了 x ,在 T1 提交之前, T2 随之也更改了 x ,这就是脏写,这时因为 T1 还没有提交,所以 T2 更改的就是 T1 的中间状态。假如现在 T2 提交了, T1 就要回滚,如果回滚到 T1 开始前的状态,已经提交的 T2 对 x 的操作就丢失了;假如不回滚到 T1 开始前的状态,已经 Roll Back 的 T1 的影响就还存在于数据库中。能够允许这种现象的数据库基本是不可用的,因为它已经不能完成事务的 Roll Back 了。
- 脏读(Dirty Read),即有两个事务 T1 和 T2 , T1 更改了 x ,将 x 从 0 修改为 5 ,在 T1 提交之前, T2 对 x 进行了读取操作,读到 T1 的中间状态 x = 5 ,这就是脏读。假设最终 T1 Roll Back 了,而 T2 却根据 T1 的中间状态 x = 5 做了一些操作,那么最终就会出现不一致的结果。
- 不可重复读(Nonrepeatable read)/ 读倾斜(Read Skew),即有两个事务 T1 和 T2 , T1 先读了 x = 0 ,然后 T2 更改了 x = 5 ,接着提交成功,这时如果 T1 再次读取 x = 5 ,就是不可重复读。不可重复读会出现在一个事务内,两次读同一个数据而结果不一样的情况。
- 丢失更新(Loss of Update),即有两个事务 T1 和 T2 , T1 先读 x = 0 ,然后 T2 读 x = 0 ,接着 T1 将 x 加 3 后提交, T2 将 x 加 4 后提交,这时 x 的值最终为 4 , T1 的更新丢失了,如果 T1 和 T2 是串行的话,最终结果为 7 。
- 幻读(Phantom Read),即有两个事务 T1 和 T2 , T1 根据条件 1 从表中查询满足条件的行,随后 T2 往这个表中插入满足条件 1 的行或者更新不满足条件 1 的行,使其满足条件 1 后提交,这时如果 T1 再次通过条件 1 查询,则会出现在一个事务内,两次按同一条件查询的结果却不一样的情况。
- 写倾斜(Write Skew),即假如 x , y 需要满足约束 x + y >= 0 ,初始时 x = -3 , y = 5 ,事务 T1 先读 x 和 y ,然
两阶段提交
两阶段提交协议(Two Phase Commit)是分布式事务中的一种常用协议,算法思路可以概括为参与者将操作成败通知协调者,再由协调者根据所有参与者的反馈情况决定各参与者要提交操作还是中止操作。
它可以分为两个阶段:准备阶段和提交阶段。
- 准备阶段,协调者让参与者执行事务,但是并不提交,协调者返回执行情况。这个阶段参与者会记录 Redo 和 Undo 信息,用于后续提交或者回滚。
- 提交阶段,协调者根据准备阶段的情况,要求参与者提交或者回滚,参与者返回提交或者回滚的结果。准备阶段任何一个节点执行失败了,就都会回滚。全部执行成功就提交。
两阶段提交协议的缺点很多。最大缺点是在执行过程中节点都处于阻塞状态。也就是节点之间在等待对方的响应消息时,什么也做不了。特别是如果某个节点在已经占有了某项资源的情况下,为了等待其他节点的响应消息而陷入阻塞状态时,当第三个节点尝试访问该节点占有的资源时,这个节点也会连带着陷入阻塞状态。
三阶段提交
因此在两阶段提交的基础上,三阶段提交引入了一个新阶段,协调者会先问一下参与者能不能执行这个事务。所以整个三阶段提交协议的三个阶段是这样的:
- 第一阶段(CanCommit):协调者问一下各个参与者能不能执行事务。参与者这时候一般是检查一下自己有没有足够的资源。
- 第二阶段(PreCommit):类似于两阶段提交的第一个阶段,执行事务但是不提交。
- 第三阶段(Commit):直接提交或者回滚。
目前看来,三阶段提交协议并没有两阶段提交协议使用得那么广泛,原因有两个,一是两阶段提交协议已经足以解决大部分问题了,二是三阶段提交协议的收益和它的复杂度比起来,性价比有点低。
- 点赞
- 收藏
- 关注作者
评论(0)