MySql——关于事务那些事
仅作为笔记,码字不易,转载请标明出处。
文章目录
前言
仅作为笔记
一、认识事务
1.1 概述
- 定义:事务是访问并更行数据库中各种数据项的一个程序执行单元,事务可由一条非常简单的SQL语句组成,也可以由一组复杂的SQL语句组成。
- 事务的四大特性:
1)原子性:指整个数据库事务是不可分割的工作单位,只有使事务中所有的数据库操作都执行成功,才算整个事务成功。
2)一致性:事务将数据库从一种状态转变为下一种一致的状态,从事务开始之前到事务结束以后,数据库的完整性约束没有被破坏。
3)隔离性:也可以叫做并发控制、可串行化、锁等,要求每个读写事务的对象对其他事务的操作对象能相互分离,即该事务提交前对其他事务都不可见,通常使用加锁来实现。
4)持久性:事务一旦提交,结果就是永久性的,即使发生宕机障碍,数据库也能将数据恢复,这里说的永久不包括不可抗力因素造成的。
1.2 分类
- 扁平事务:事务类型中最基本最简单最常用的。
- 带有保存点的扁平事务:扁平事务一旦发生错误只能回滚到最初状态,在某些情况下这样的代价实在太大了,所以设置了保存点用来事务回滚。
- 链事务:因为带有保存点的扁平事务的保存点容易丢失,在提交一个事务时,释放不需要的数据对象,将必要的处理上下文隐式的传给下一个要开始的事务。与保存点的扁平事务不一样(可以回滚到任意正确的保存点),链事务的回滚仅限于当前事务。
- 嵌套事务:了解即可
- 分布式事务:了解即可
二、事务的实现
2.1 redolog
- redolog也称为事务日志,由InnoDB存储引擎层产生。记录的是数据库中每个页的修改,而不是某一行或某几行修改成怎样,可以用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置,因为修改会覆盖之前的)。
- 为什么需要redolog?因为数据库在运行中难免会遇到崩溃的情况,如果崩溃时正在写非常重要的东西会导致数据丢失。所以需要有一个东西来保证事务顺利的进行持久化!
- 为什么恢复数据页只能恢复到最后一次提交的位置?因为redolog是先写进内存中的,在合适的时候才会将redolog写进磁盘里,这样是为了避免过多的进程磁盘访问。而意味着redolog大小是固定的,从而需要对这块内存循环使用,那么就不得不对这块内存定时清理,所以只能恢复到最后一次提交的位置,之前的都被覆盖了。如下是循环使用示意图:
write pos是当前记录的位置,一边写一边后移,写到第3号文件末尾后就回到0号文件开头。
checkpoint是当前要擦除的位置,也是往后推移并且循环的,擦除记录前要把记录更新到数据文件。
write pos和checkpoint之间的可以用来记录新的操作。如果write pos追上checkpoint,表示 redolog 满了,需要停下来先擦掉一些记录,把checkpoint推进一下。
有了redolog,InnoDB就可以更好的保证即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。
2.2 undolog
- undolog顾名思义,主要就是提供了回滚的作用,但其还有另一个主要作用,就是多个行版本控制(MVCC),保证事务的原子性。在数据修改的流程中,会记录一条与当前操作相反的逻辑日志到undolog中(可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录),如果因为某些原因导致事务异常失败了,可以借助该undolog进行回滚,保证事务的完整性,所以undolog也必不可少。
2.3 binlog
- binlog在MySQL的server层产生,不属于任何引擎,主要记录用户对数据库操作的SQL语句(除了查询语句)。binlog主要用作主从同步和数据库基于时间点的还原。
- binlog也称为归档日志,不会像redolog一样擦掉之前的记录循环写,而是一直记录(超过有效期才会被清理),如果超过单日志的最大值(默认1G,可以通过变量 max_binlog_size 设置),则会新起一个文件继续记录。但由于日志可能是基于事务来记录的(如InnoDB表类型),而事务是绝对不可能也不应该跨文件记录的,如果正好binlog日志文件达到了最大值但事务还没有提交则不会切换新的文件记录,而是继续增大日志,所以 max_binlog_size 指定的值和实际的binlog日志大小不一定相等。
- binlog可以简化掉吗?
主从模式下:binlog是必须的,因为从库的数据同步依赖的就是binlog;
单机模式下:如果不考虑数据库基于时间点的还原,binlog就不是必须,因为有redolog就可以保证crash-safe能力了;但如果万一需要回滚到某个时间点的状态,这时候就无能为力,所以建议binlog还是一直开启;
小结:在主从模式下,三个日志都是必须的;在单机模式下,binlog可以视情况而定,保险起见最好开启。
三、隐式提交的SQL语句
就是有一些语句在执行完了后会隐式的做出一个COMMIT操作,如下:
四、事务的隔离级别和实现原理
先知道几个概念:
- 脏读
脏读指的是读到了其他事务未提交的数据,未提交意味着这些数据可能会回滚,也就是可能最终不会存到数据库中,也就是不存在的数据。读到了并不一定最终存在的数据,这就是脏读。 - 可重复读
可重复读指的是在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据都是一致的。通常针对数据更新(UPDATE)操作。 - 不可重复读
不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新(UPDATE)操作。 - 幻读
针对数据插入(INSERT)操作来说的。连续执行两次同样的SQL语句可能导致不同的结果,第二次的SQL语句可能返回之前不存在的行。实际上是因为当前事务能看到其他事务的执行结果引起的,就像幻觉一样凭空产生了一些值。
事务的隔离级别有四个,如下:
- 读未提交(READ UNCOMMITTED):一个事务可以读到其他事务未提交的数据,事务隔离其实是依靠锁来实现的,加锁自然会带来性能的损失。而读未提交隔离级别是不加锁的,没加锁从而会导致脏读、幻读、不可重复读。
- 读提交 (READ COMMITTED):一个事务只能读到其他事务已经提交了的数据,意味着避免了脏读。由于每个 select 语句都有自己的一份快照,而不是一个事务一份,所以在不同的时刻,查询出来的数据可能是不一致的(有其他事务相同值被改变了,但是当前事务的select快照里面的值没改变),所以没办法避免不可重复读,当然幻读更不可能避免。
- 可重复读 (REPEATABLE READ):实现了避免脏读和不可重复读,注意,InnoDB的可重复读之所以解决了幻读问题靠的是间隙锁加行级锁解决的。
- 串行化 (SERIALIZABLE):屏蔽了所有的问题。
图表总结如下: