事务是用户定义的一个数据库操作系列,这些操作要么全做要么全不做,是一个不可分割的工作单位。
事务是数据库中重要的概念,通常以BEGIN TRANSACTION开始,以COMMIT或者ROLLBACK结束。数据库事务具有四个著名的特性:ACID。
原子性(Automatic):即事务中的操作要么都做,要么都不做。
一致性(Consistency):事务的执行的结果必须是使数据库从一个一致性状态到另一个一致性状态。这里的一致性状态指的是物理和逻辑上的数据库一致性状态。比如一个银行系统内部各个用户直接互相转账,无论如何转账,系统内部的金额的总数总是恒定不变的。
隔离性(Isolation):一个事务的执行不能被其他事务干扰。即一个事务内部的操作及使用的数据对其他并发事务是隔离的,并发执行的各个事务之间不能相互干扰。
持久性(Duration):一个事务一旦提交,它对数据库中数据的改变就该是永久性的。
事务的ACID特性需要数据库管理系统保证,同时事务也是数据库管理系统恢复和并发控制的基本单位。
数据库并发控制
事务是并发控制的基本单位,由于并发控制可能会导致事务的ACID特性遭到破坏,从而带来了数据库的数据不一致性。具体来讲,由于并发操作,多个事务的并发执行会带来如下四个典型的数据不一致性问题,而这几种问题主要是由于并发操作破坏了事务的隔离性(Isolation)导致的。
丢失修改
这种问题是最直观,最不应该发生的,例如两个事务T1和T2都读入同一数据进行修改,这样T1提交修改结果后,随即T2也提交了结果。这样,T2便将T1对数据的修改给覆盖了,这样就出现了丢失修改的问题了。
这种问题发生的最主要原因是:两个或多个事务都在对同一数据项进行写操作。
不可重复读
不可重复读指的是:事务T1读取了某个数据项A,在事务T1运行期间又有其他事务例如T2对数据项A进行了修改并提交了,这样事务T1再次读取数据项A的时候,发现结果不一致了。
这种问题发生的最主要原因是:一个事务在对某一个数据项进行读操作,而另外的其他事务对该数据项进行了写操作。
读脏数据
读脏数据指的是:事务T1正在修改某个数据项A,事务还未提交,而事务T2读取了数据项A,但是随后事务T1由于某种原因回滚了事务(并未提交事务),此时事务T2读取到的数据项A的值和数据库中的值是不一致的,称事务T2读了脏数据(Read Dirty)。
这种问题发生的最主要原因是:一个事务在对某一个数据项进行写操作,而另外的其他事务对该数据项进行了读操作。
幻读
幻读指的是:事务T1按照某些条件从数据库中查询了一些数据记录,但是在事务T1执行过程中,事务T2插入或者删除了某些数据记录,这样当事务T1再一次按照同样的条件从数据库查询的时候,发现多了或者少了某些记录。幻读本质上属于不可重复读,这里为了隔离级别作对照,所以将其单独划出来作为幻读。
产生的原因同不可重复读类似:由于事务T1按条件对数据表中的很多数据项查询的时候,这时候其他事务T2对数据表进行了其他数据项的写操作(插入、修改或者删除)。
封锁是实现并发控制的一个非常重要的技术。所谓封锁就是在事务对某个数据对象例如表、记录等进行操作的时候(读或写),先向系统请求对其加锁,成功加锁后,该事务才可以继续对其进行操作;否则,只能等待直到加锁成功。
具体来说:基本的锁分为两种:
排他锁(Exclusive Locks)又称写锁,X锁;
共享锁(Share Locks)又称读锁,S锁;
采用封锁确实能来保证并发下的数据库事务的特性,但是封锁带来了额外的系统开销,进而影响到系统的并发度。
例如1:当事务T1对某个数据项A进行写操作的时候,为避免读脏数据, 那么所有的其他向对数据项A进行读取的事务都得进行等待,那么系统此刻的并发度就大大降低了。
例如2:为避免幻读的产生,当事务T1对某一表Table中的某几行记录进项查询的时候,由于封锁了整个表Table,此时所有其他想对该表Table进项增加删除记录的事务都得进行等待,由于封锁的数据对象粒度太大,导致并发度大大降低。
综上可以看出:封锁和并发度本来就是一种矛盾,一方面我们想要尽可能的保证事务的隔离性进而避免数据不一致的问题;另一方面我们有希望系统的并发度不要下降。
既然是矛盾,那就只能妥协,为了充分满足不同的应用场景,MySQL数据库InnoDB支持4种不同的事务隔离级别。
事务隔离级别
考虑到封锁和并发度的权衡,MySQL为用户提供了四种不同的事务隔离性级别。并发度从低到高(数据一致性强度从高到低)分别是:
SERIALIZABLE(可串行化)可避免脏读、不可重复读、幻读情况的发生。
REPEATABLE READ(可重复读)可避免脏读、不可重复读情况的发生。
READ COMMITTED(读已提交)可避免脏读情况发生。
READ UNCOMMITTED(读未提交)最低级别,以上情况均无法保证。
两个事务的X锁和S锁可以构成一个相容矩阵如下:
含义就是:T1对数据项加了X锁,则T2不能对其加S和X锁,T2对数据项加了S锁,则T1可以对其加S锁而不能加X锁。(相容矩阵的加锁顺序都是是T1先加锁,T2后加锁)
(1)READ UNCOMMITTED(读未提交)最低级别,以上情况均无法保证。
就是一个事务可以读取另一个未提交事务的数据。
官网给出建议:该隔离级别仅仅能做查询
由于其不能保证读脏数据,因此S锁和X是相容的,因此可能如下:
(2)READ COMMITTED(读已提交)可避免脏读情况发生。
就是一个事务要等另一个事务提交后才能读取数据。
读已提交,避免脏读,但是不能保证可重复读;说明T1的X锁和T2的S锁不相容;而S锁和X锁相容的。也就是保证了某个事务在写数据项的时候,其他事务是不能读写该数据项的。
(3)REPEATABLE READ(可重复读)可避免脏读、不可重复读情况的发生。
就是在开始读取数据(事务开启)时,不再允许修改操作
可重复读,避免了脏读和不可重复读;说明T1的X锁和T2的S锁、T1的S锁和T2的X锁都不相容。也就是保证了某个事务在写某个数据项的时候,其他事务不能读写该数据项;且在读某个数据项时,其他事务只能读该数据项。
(4)SERIALIZABLE(可串行化)可避免脏读、不可重复读、幻读情况的发生。
Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。
这能保证该事务调度策略的运行结果同某一种串行调度结果一致,但是这会极大的降低并发度;同时,这也是MySQL的InnoDB提供的最高的事务隔离级别,这里的相容矩阵同3。
事务隔离级别举例:
数据库事务的隔离级别有4种,由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable 。而且,在事务的并发操作中可能会出现脏读,不可重复读,幻读。下面通过事例一一阐述它们的概念与联系。
Read uncommitted
读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。
事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。
分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。
那怎么解决脏读呢?Read committed!读提交,能解决脏读问题。
Read committed
读提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。
事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的…
分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。
那怎么解决可能的不可重复读问题?Repeatable read !
Repeatable read
重复读,就是在开始读取数据(事务开启)时,不再允许修改操作
事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。
分析:重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。
什么时候会出现幻读?
事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。
那怎么解决幻读问题?Serializable!
Serializable 序列化
Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。
值得一提的是:大多数数据库默认的事务隔离级别是Read committed,比如Sql Server,Oracle。Mysql的默认隔离级别是Repeatable read。