在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。
- 读未提交:直接返回记录上的最新值 没有视图概念
- 读提交:视图从每个SQL语句开始执行的时候创建的
- 可重复读:事务启动时创建的,整个事务存在期间都用这个视图
- 串行化:直接用加锁的方式避免并行访问
事务隔离的实现
以可重复读为例
在MySQL里,每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。
假设一个值从1被按顺序改成了2、3、4,在回滚日志里面就会有类似下面的记录。
当前值是4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的read-view
。在视图A,B,C里,这一个记录的值分别是1,2,4
,同一条记录在系统里可以存在多个版本,这就是数据库的多版本并发控制(MVCC
)。对于read-view A
,要得到1,就必须要将当前值依次执行图中所有回滚操作得到。
同时,即使现在有另一个事务正在把4改成5,这个事务跟read-vide A,B,C
对应的事务是不会冲突的。
那么这些回滚日志什么时候会被删除呢?当系统判断,没有事务再需要用到这些回滚日志时,回滚日志会被删除,也就是当系统里没有比这个回滚日志更早的read-view
的时候。
关于长事务的使用说明:长事务意味着系统里会存在很老的事务视图,由于这些事务随时可以访问数据库里的任务数据,所以这个事务提交之前,数据库里面他可能用到的回滚记录都必须保留,就会导致占用大量的存储空间。所以尽量避免使用长事务。
事务启动方式
- 显式启动事务语句,
begin或start transaction
。配套的提交语句是commit
,回滚语句是rollback
set autocommit=0
,这个命令会将这个线程的自动提交关掉,意味着如果只执行一个select
语句,这个事务就启动了,而且不会自动提交。这个事务会持续存在直到你主动执行commit/rollback
语句,或者断开连接。
有些客户端连接框架会默认连接成功后先执行一个set autocommit=0
的命令。这就导致接下来的查询都在事务中,如果是长连接,就导致了意外的长事务。因此建议总是使用set autocommit=1
, 通过显式语句的方式来启动事务。
但是有的开发同学会纠结“多一次交互”的问题。对于一个需要频繁使用事务的业务,第二种方式每个事务在开始时都不需要主动执行一次 “begin”,减少了语句的交互次数。如果你也有这个顾虑,建议使用
commit work and chain
语法。在
autocommit
为1
的情况下,用begin
显式启动的事务,如果执行commit
则提交事务。如果执行commit work and chain
,则是提交事务并自动启动下一个事务,这样也省去了再次执行begin语句的开销。同时带来的好处是从程序开发的角度明确地知道每个语句是否处于事务中。