1. 数据库的事务机制
1.1 为什么需要事务
在实际中存在并发访问数据的问题,比如当A在查询数据库里的某条记录时,同一时刻B对这条记录进行了修改,那么A就有可能读取到有问题的脏数据。数据库的事务机制,就是为了解决并发访问所产生的问题。
事务的基本操作:
1. 开启一个事务
2. 进行一系列的数据操作
3. 提交或回滚事务,提交成功则将数据持久化到数据库,否则该事务中的所有数据操作全部无效
1.2 事务的ACID原则
事务具备四个特性:原子性、一致性、隔离性、持久性(ACID原则)
原子性(Atomic)
一个事务内的所有操作都是原子性的,要么全部成功,要么全部失败
一致性(Consistency)
在完成一个事务操作后应该使数据库从一个一致性状态达到另一个一致性状态,比如在转账功能中,A转账给B一百元,那么B的账号增加一百元,而A的账号也应该减少一百元。
一致性可以说是ACID原则中最基础的特性,其他三个特性都是为了保证一致性而存在的。关于数据库的一致性更加具体的说明可以参考知乎的该问答
隔离性(Isolation)
不同事务之间的操作互不影响,数据库对于事务存在着不同的隔离级别,详情请见下文。
持久性(Durability)
事务的操作一旦完成,必须持久化到数据库中。
1.3 并发访问数据会造成什么问题
并发访问数据会出现三种情况:脏读、不可重复读、幻读。
脏读(Dirty Read)
当事务A在修改(insert、delete、update)记录但还未提交时,另一个事务B读取了该记录,此时事务B读到的数据就是脏数据,如果此时事务A回滚了事务,则事务B读到的数据无效。
不可重复读(Non-repeatable Read)
当事务A在读取记录时,另一个事务B修改(delete、update)了该记录并提交,此时事务A再一次读取该记录会读取到被事务B修改后的数据。在一个事务中前后两次读取的数据不一样,因此被称作不可重复读。
幻读(Phantom Read)
当事务A在读取记录时,另一个事务B插入(insert)了新记录并提交,导致事务A再次查询记录时多出了原本不存在的记录,就像看到了幻觉一样,所以被称作幻读。
1.4 事务的隔离级别
事务的隔离级别描述了事务被隔离的程度,其隔离级别有4个,由低到高依次为Read Uncommitted 、Read Committed 、Repeatable Read 、Serializable。通过设置不同的事务隔离级别可以解决脏读、不可重复读和幻读。
读未提交(Read Uncommitted)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。在实际中很少使用这种隔离级别,其性能并没有比其他隔离级别好多少,且会发生脏读、不可重复读和幻读。
读提交(Read Committed)
在该隔离级别,所有事务都只能看到其他已经提交的事务的执行结果。这是大多数数据库系统的默认隔离级别(MySQL默认的是可重复读),在该隔离级别会发生不可重复读和幻读。
可重复读(Repeatable Read)
这是MySQL默认的事务隔离级别,该隔离级别虽然解决了脏读和不可重复读,但还是无法避免幻读。
可串行化(Serializable)
最高级别的事务隔离级别,通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。但是系统开销花费最高,性能很低,一般很少使用。
- √表示发生,×表示不会发生
脏读 | 可重复读 | 幻读 | |
---|---|---|---|
读未提交 | √ | √ | √ |
读提交 | × | √ | √ |
可重复读 | × | × | √ |
可串行化 | × | × | × |
1.5 如何查询并设置数据库中的事务隔离级别
1.5.1 MySQL
MySQL支持四种事务隔离级别,默认的事务隔离级别是Repeatable Read
查询事务隔离级别
//查询下一个(未开启)事务的隔离级别
select @@tx_isolation;
//查询当前会话事务的隔离级别
select @@session.tx_isolation;
//查询全局事务的隔离级别
select @@global.tx_isolation;
修改事务隔离级别
set [SESSION | GLOBAL] transaction isolation level {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
如果不添加session或者global,则是为下一个(未开启)事务设置隔离级别,比如
//为下一个(未开启)事务设置read uncommitted隔离级别
set transaction isolation level read uncommitted
如果添加了session关键字则表示为当前连接上执行的事务设置默认的隔离级别;如果添加了global关键字则表示在全局为所有新创建的连接上执行的事务设置默认的隔离级别
还可以使用另一种方式来设置隔离级别
set @@tx_isolation = 'read-committed';
set @@session.tx_isolation = 'repeatable-read';
set @@global.tx_isolation = 'read-uncommitted';
1.5.2 Oracle
Oracle只支持两种事务隔离级别Read Committed和Serializable,所以Oracle不支持脏读。Read Only是Serializable的子集,两者都避免了不可重复读和幻读,区别是Read Only不允许在事务中进行DML操作,而Serializable可以。Oracle的默认事务隔离级别是Read Committed。
设置事务隔离级别
SET TRANSACTION ISOLATION LEVEL [READ COMMITED | SERIALIZABLE | READ ONLY];
设置一个会话的隔离级别
ALTER SESSION SET ISOLATION_LEVEL [READ COMMITTED| SERIALIZABLE];
事务的隔离级别和锁机制的关系
为了维护数据库事务的ACID四大特性,数据库一般使用加锁这种方式。事务隔离级别是为了有效保证并发读取数据的正确性,而数据库的锁机制,则是为了构建这些隔离级别而存在的。
关于数据库的锁机制,可以参考这篇文章