事务的特性
ACID(Atomicity、Consistency、Isolation、Durability,即 原子性、一致性
隔离性、持久性)
隔离级别
为了解决多个事务的同时出现而出现的脏读(dirty read)、幻读(phantom read)、不可重复读问题,有了事务的隔离级别。
隔离级别有四种,依次逐渐提高
- 读未提交
- 读已提交
- 可重复读
- 串行化
注意:事务的隔离级别越高,效率也就越低,并行性能下降,安全性一次提高。
读未提交
指的当两个事务在执行(A 事务、B 事务),A 事务对表中数据进行修改,但是还未提交,而 B 可以看到 A 中已经修改的值。
读已提交
指的当两个事务在执行(A 事务、B 事务),A 事务对表中数据的修改,在提交之后,B 事务才能看到 A 中修改的最新值。
可重复读
指的当两个事务在执行(A 事务、B 事务),A事务无论怎么修改,提交,而 B 事务所看到的,都只能看到刚开始的数据,也就是 B 事务整个过程中,数据看到的和前后保持一致。
可用于在银行记账数据校队的时候,毕竟你不想在对账的时候,又看到新插入的数据。
串行化
指的当两个事务在执行(A 事务、B 事务),A 事务如果进行修改,就会开启 “写锁”,读取数据,则会开启 “读锁”,而 B 事务在对同一个字段操作时,只能等待 A 事务操作完毕,像排队一样,我的事务未提交,你就别想改数据。
例子
当 id 初始值为 1 的时候,下面两个事务 A 、B 事务执行
- 读未提交情况
- A 第一次读取 Id 值为 1 ,第二次读取 Id 为 2,因为修改的过程可以被 A 看到,最后第三次也为 2
- 读已提交情况
- A 第一次、第二次读取 Id 都为 1,事务得在提交后,才能被 A 看到,所以第三次为 2
- 可重复度
- A 从事务开启到结束阶段读取的 Id 值都为 1,第三次为 2,因为可重复读,从头到尾读取的数据都保持一致。
- 串行化
- 事务 B 执行“将 1 改成 2”的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, 第 1、2次值是 1,第三次的值是 2。
事务实现
实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。
-
读未提交
- 它总是获取数据库中最新值,没有创建视图
-
读提交
- 视图是在 SQL 语句执行的时候创建出来的。
-
可重复的
- 是在事务启动的时候创建的,整个事务存在期间都用这个视图
-
串行化
- 在这种情况下,直接用加锁的方式解决并行访问的问题。
注意:oracle 的事务默认隔离级别为读提交。通过设置启动参数 transaction-isolation
值设置为 READ-COMMITTED
也就是读提交。show variables
可以查看当前值。
在 MySQL 中,每一条记录更新都同时会记录一条回滚操作,记录在回滚日志中。通过回滚操作可以得到之前的状态值。
同一条记录在系统中有多个版本,因为回滚日志记录的操作有多个,不同阶段视图看到的也有多个。这就是数据库的多版本并发控制(MVCC)
回滚日志几时删除?
系统会判断,当没有事务需要用到这些回滚日志,会情况。如何没有需要?就是当系统里没有比这个回滚日志更早的 read-view 的时候
为什么尽量不用使用长事务?
长事务意味系统存在一个很老的视图,在这个事务提交之前,回滚日志就要继续保留,导致占用大量存储,并且还长时间占用锁资源。
启动方式
- 显式启动事务语句,begin 或者 start transaction 开启事务,commit 提交事务,rollback 回滚事务
- 通过设置 set autocommit = 0,将自动提交关闭,只有执行语句,如 select ,就开启事务,直到主动的 commit 和 rollback。
问题:
set autocommit 有些客户端连接框架会默认连接成功后先执行一个 set autocommit =0 的命令。这就导致接下来的查询都在事务中,如果是长连接,就导致了意外的长事务。
建议:
使用 set autocommit = 1 ,显示语句来启动事务和提交关闭事务。考虑到多一次执行 begin,可以使用 commit work and chain 语法(表示提交事务并自动启动下一个事务)