数据库事务可以被定义为一个或者几个数据库允许的操作的集合。这个集合需要支持ACID特性。
原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚。因此事务的操作如果成功就必须要完全应用到数据库,如果操作失败则不能对数据库有任何影响。
一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离。
关于事务的隔离性数据库提供了多种隔离级别,稍后会介绍到。
持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下也不会丢失提交事务的操作。在提交事务方法后,提示用户事务操作完成,当我们程序执行完成直到看到提示后,就可以认定事务已经正确提交,即使这时候数据库出现了问题,也必须要将我们的事务完全执行完成,否则就会造成我们看到提示事务处理完毕,但是数据库因为故障而没有执行事务的重大错误。
当一个事务正在多次修改某个数据,而这个事务中多次修改还没有提交,这时一个并发的事务来访问该数据,就会造成两个事务得到的数据不一致。
不可重复读与脏读之间的区别在于:脏读是一个事务读取了另一个事务未提交的脏数据,而不可重复读是一个事务读取了另一个事务提交的数据。
如果事务中都使用快照读,那么就不会产生幻读现象,但是快照读和当前读混用就会产生幻读。关于快照读与当前读可参见数据库快照 (SQL Server)。
幻读和不可重复读都是读取了另一个已经提交的事物(脏读读取的是未提交的事物),不同之处在于不可重复读查询的是同一个数据项,但是幻读针对的是一批数据整体(比如数据的个数)。
“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决。
数据库实现事务隔离的方法可以分为以下两种:
为了解决“隔离”与“并发”的矛盾,ISO/ANSI SQL92定义了4个事务隔离级别,每个级别的隔离程度不同,允许出现的副作用也不同,应用可以根据自己的业务逻辑要求,通过选择不同的隔离级别来平衡 “隔离”与“并发”的矛盾。
这是数据库最高的隔离级别,这种级别下,事务“串行化顺序执行”,也就是一个一个排队执行。
这种级别下,“脏读”、“不可重复读”、“幻读”都可以被避免,但是执行效率奇差,性能开销也最大,所以基本没人会用。
可重复读,顾名思义,就是专门针对“不可重复读”这种情况而制定的隔离级别,自然,它就可以有效的避免“不可重复读”。而它也是MySql的默认隔离级别。
在这个级别下,普通的查询同样是使用的“快照读”,但是,和“读提交”不同的是,当事务启动时,就不允许进行“修改操作(Update)”了,而“不可重复读”恰恰是因为两次读取之间进行了数据的修改,因此,“可重复读”能够有效的避免“不可重复读”,但却避免不了“幻读”,因为幻读是由于“插入或者删除操作(Insert or Delete)”而产生的。
读提交,顾名思义,就是只能读到已经提交了的内容。这是各种系统中最常用的一种隔离级别,也是SQL Server和Oracle的默认隔离级别,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”,但不能避免“幻读”和“不可重复读取”。该级别适用于大多数系统。这里多说点:那为什么“读提交”同“读未提交”一样,都没有查询加锁,但是却能够避免脏读呢?
这就要说道另一个机制“快照(snapshot)”,而这种既能保证一致性又不加锁的读也被称为“快照读(Snapshot Read)”
假设没有“快照读”,那么当一个更新的事务没有提交时,另一个对更新数据进行查询的事务会因为无法查询而被阻塞,这种情况下,并发能力就相当的差。而“快照读”就可以完成高并发的查询,不过,“读提交”只能避免“脏读”,并不能避免“不可重复读”和“幻读”。
读未提交,顾名思义,就是可以读到未提交的内容。因此,在这种隔离级别下,查询是不会加锁的,也由于查询的不加锁,所以这种隔离级别的一致性是最差的,可能会产生“脏读”、“不可重复读”、“幻读”。如无特殊情况,基本是不会使用这种隔离级别的。
这四种事物隔离级别是否会产生以上三个问题总结如下:
事物隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
串行化(S,serializable) | 否 | 否 | 否 |
可重复读(RR,repeatable-read) | 否 | 否 | 是 |
读提交(RC,read-committed) | 否 | 是 | 是 |
读未提交(RU,read-uncommitted) | 是 | 是 | 是 |
以上四种隔离级别最高的是Serializable级别,最低的是Read uncommitted级别,当然隔离级别越高,越能保证数据的完整性和统一性,但是执行效率就越低,对并发性能的影响也越大。像Serializable这样的级别,就是以锁表的方式(类似于Java多线程中的锁)使得其他的线程只能在锁外等待,所以平时选用何种隔离级别应该根据实际情况。