事务
特性
事务主要是用于处理并发和从错误状态回滚到正常状态的一个操作,对于事务,有个很经典的例子:
A 打算给 B 转 200元, 则在数据库中应该进行的操作应该是下面这样:
- A 账户余额-200元;
- B 账户余额+200元;
但是如果这两条是分别执行的话, 万一 1 和 2 之间出现了什么问题(网络波动啦,临时工写的代码出Bug了啥的),那是不是 A 就白白扣了200元,而这多扣的 200 就离奇失踪了?在这种情况下,就需要事务了.
事务可以把这两个操作打包执行,也就是一荣俱荣,一损俱损.如果给 B 账户余额+200失败,则A账户余额-200也就默认失败.换句话说,事务是一个整体,而这也就是事务的原子性(Atomicity)
当然事务肯定不止这一个特性,实际上,事务特性共有4个,分别是原子性,一致性,隔离性,持久性,一般都把这四个特性简称为ACID
.这些特性描述如下:
- 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
- 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
- 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
- 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。
使用事务
SQL国际标准使用START TRANSACTION
(或者是BEGIN
)开始一个事务。COMMIT语句使事务成功完成。ROLLBACK
语句结束事务(回滚),放弃从START TRANSACTION
开始的一切变更。若autocommit被START TRANSACTION
的使用禁止,在事务结束时autocommit会重新激活。
当然还有一些其他的,比如可以设置标记节点:
SAVEPOINT identifier
,SAVEPOINT 允许在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT;RELEASE SAVEPOINT identifier
删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;ROLLBACK TO identifier
把事务回滚到标记点;
1 | --被BEGIN TRANSACTION 和 COMMIT 包裹的 SQL语句 具有ACID特性 |
隔离级别
在讲事务的隔离级别之前,我们需要先了解一下不考虑事务的隔离性可能会出现的异常
- 脏读(Dirty Read):当一个事务读取另一个事务尚未提交的修改时,产生脏读。
- 不可重复读(Nonrepeatable Read):一个事务对同一行数据重复读取两次,但是却得到了不同的结果(在查询间隔中查询内容被另一个事务修改并提交了)
- 幻读(Phantom Reads):事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。
- 丢失修改(Lost Update):
- 当两个事务更新相同的数据源,如果第一个事务被提交,第二个却被撤销,那么连同第一个事务做的更新也被撤销
- 有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。
而为了兼顾并发效率和异常控制,在SQL标准规范中,提供了四种隔离级别.分别是序列化,可重复读,读已提交,读未提交,详情如下:
- Serializable (序列化):可避免脏读、不可重复读、幻读的发生。
- Repeatable read (可重复读):可避免脏读、不可重复读的发生。
- Read committed (读已提交):可避免脏读的发生。
- Read uncommitted (读未提交):最低级别,任何情况都无法保证。
换成表格如下(Y表示能避免,N表示不能避免):
隔离级别名 | 级别号 | 脏读 | 不可重复读 | 幻读 | 备注 |
---|---|---|---|---|---|
Read uncommitted(读未提交) | 0 | N | N | N | |
Read committed (读已提交) | 1 | Y | N | N | 大多数数据库默认的隔离级别 |
Repeatable read(可重复读) | 2 | Y | Y | N | InnoDB默认级别 |
Serializable(序列化) | 3 | Y | Y | Y |
以上就是级别详情,需要注意的是:隔离级别越高,效率越低.
在 MySQL 中,可以使用select @@tx_isolation;
查询当前事务的隔离级别,可以用
1 | set [glogal | session] transaction isolation level Read uncommitted/Read committed/Repeatable read/Serializable; |
在开启事务前设置事务的四种隔离级别.
事务隔离的实现
事务隔离可以借助于锁实现. 这里只是大致思路,MySQL具体实现参照[MySQL 5.7 Reference Manual]
- Read Uncommited
- 事务在读数据时候未对数据加锁
- 事务在修改数据的时候只对数据增加行级共享锁
- Read Committed
- 事务对当前被读取的数据加行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁
- 事务在更新某数据的瞬间,必须对其加行级排他锁,直到事务结束才释放
- Repeatable Read
- 事务在读取某数据的瞬间,必须先对其加行级共享锁,直到事务结束才释放
- 事务在更新某数据的时候,必须先对其加行级排他锁,直到事务结束才释放
- Serializable
- 事务在读取数据时,必须先对其加表级共享锁,直到事务结束才释放
- 事务在更新数据时,必须先对其加表级排他锁,直到事务结束才释放