事务ACID性质
传统的关系型数据库中,常常用ACID性质来检验事务功能的可靠性和安全性。
事务总是具有 原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和耐久性(Durability)。
原子性是基础,隔离性是手段,持久性是目的,真正的老大就是一致性。
原子性
原子性:数据库将事务中的多个操作当做一个整体来执行,服务器要么就执行事务中的所有操作,要么一个操作也不执行。
一致性
一致性:如果数据库在执行事务之前是一致的,那么在事务执行之后,无论事务是否执行成功,数据库也应该是一致的。
拿银行转账来说,一致性要求事务的执行不应改变A、B 两个账户的金额总和。如果没有这种一致性要求,转账过程中就会发生钱无中生有,或者不翼而飞的现象。
隔离性
隔离性:即使数据库中有多个事务并发地执行,各个事务之间也不会相互影响。并在并发状态下执行的事务和串行执行的事务产生的结果完全相同。
耐久性
耐久性:事务的耐久性是指,当一个事务执行完毕时,执行这个事务所得的结果已经被保存到永久性存储介质(硬盘)里面了,即使服务器在事务执行完毕之后停机,执行事务所得的结果也不会丢失。
事务的并发问题
脏读(Dirty Read)
在一个事务中,读取了其他事务未提交的数据。
当事务的隔离级别为READ UNCOMMITED
时,我们在SESSION 2
中插入的未提交数据在SESSION 1
中是可以访问的。
不可重复读(Unrepeatable Read)
在一个事务中,同一行记录被访问了两次却得到了不同的结果。
当事务的隔离级别为READ COMMITED
时,虽然解决了脏读的问题,但是如果在SESSION 1
先查询了一行数据,在这之后SESSION 2
中修改了同一行数据并且提交了修改,在这时,如果SESSION 1
中再次使用相同的查询语句,就会发现两次查询的结果不一样。
不可重复读的原因就是,在
READ COMMITED
的隔离级别下,存储引擎不会在查询记录时添加行锁,锁定id = 3
这条记录。
脏读和不可重复读的区别是:脏读是读到未提交的数据;而不可重复读读到的确实是已经提交的数据,但是其违反了数据库事务一致性的要求。一般来说不可重复读是可以接受的,因为其读到的是已经提交的数据,本身并不会带来很大的问题。因此,很多数据库厂商(Oracle、Microsoft SQL Server)将其数据库事务的默认隔离级别设置为READ COMMITTED,这种隔离级别下允许不可重复读的现象。
幻读(Phantom Read)
在一个事务中,同一个范围内的记录被读取时,其他事务向这个范围添加了新的记录。
重新开启了两个会话SESSION 1
和SESSION 2
,在SESSION 1
中我们查询全表的信息,没有得到任何记录;在SESSION 2
中向表中插入一条数据并提交;由于REPEATABLE READ
的原因,再次查询全表的数据时,我们获得到的仍然是空集,但是在向表中插入同样的数据却出现了错误。
这种现象在数据库中就被称作幻读,虽然我们使用查询语句得到了一个空的集合,但是插入数据时却得到了错误,好像之前的查询是幻觉一样。
在标准的事务隔离级别中,幻读是由更高的隔离级别SERIALIZABLE
解决的,但是它也可以通过 MySQL 提供的 Next-Key 锁解决:在Next-Key Lock算法下,对于索引的扫描,不仅仅是锁住扫描到的索引,而且还锁住了这些索引覆盖的范围(gap)。因此对于这个范围内的插入都是不允许的。这样就避免了另外的事务在这个范围内插入数据导致的不可重复读的问题。
补充
不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
第一条脏读是坚决抵制的,后两条在大多数情况下可不作考虑。
MySQL事务隔离级别
事务的4种隔离级别 读未提交(RU), 读已提交(RC), 可重复读(RR), 串行
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
读已提交(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
read uncommitted : 使用查询语句不会加锁,可能会读到未提交的行(Dirty Read);
read committed: 只对记录加记录锁,而不会在记录之间加间隙锁,所以允许新的记录插入到被锁定记录的附近,所以再多次使用查询语句时,可能得到不同的结果(Non-Repeatable Read);
repeatable-read: 多次读取同一范围的数据会返回第一次查询的快照,不会返回不同的数据行,但是可能发生幻读(Phantom Read);
serializable: InnoDB 隐式地将全部的查询语句加上共享锁,解决了幻读的问题;
mysql默认的事务隔离级别为repeatable-read
补充:
- SQL规范所规定的标准,不同的数据库具体的实现可能会有些差异
- mysql中默认事务隔离级别是可重复读时并不会锁住读取到的行
- 事务隔离级别为读提交时,写数据只会锁住相应的行
- 事务隔离级别为可重复读时,如果有索引(包括主键索引)的时候,以索引列为条件更新数据,会存在间隙锁间隙锁、行锁、下一键锁的问题,从而锁住一些行;如果没有索引,更新数据时会锁住整张表。
- 事务隔离级别为串行化时,读写数据都会锁住整张表。
- 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大,鱼和熊掌不可兼得啊。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。