我们知道在关系型数据库里面事务有四个属性:
- 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
- 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
- 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
- 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。
我们假设现在有A,B不同的两个事务,并且假设事务A在事务B之前启动,在并发执行时会出现的情况无非只有两个状态提交跟未提交,我们排列组合一下得到:
- A事务启动并提交完成后B事务提交
- A事务启动并提交完成后B事务未提交
- A事务启动并未提交B事务提交
- A事务启动并未提交B事务未提交
进过排序组合后我们得出上面四种情况,而我们只要考虑这四种情况里面的第2,3点。以下我们来分析1,2,3点的情况,大致可划分为A事务提交跟未提交。
而关系型数据库里面对数据的无非是四个基本操作,增,删,改,查。假设现在有A,B两个不同的事务,我们根据事务的状态个四个基本操作组合一下,会得到以下结果。
A:A事务已提交情况
- A事务增加数据已提交B事务增加数据
- A事务增加数据已提交B事务删除数据
- A事务增加数据已提交B事务修改数据
- A事务增加数据已提交B事务查询数据
- A事务删除数据已提交B事务增加数据
- A事务删除数据已提交B事务删除数据
- A事务删除数据已提交B事务修改数据
- A事务删除数据已提交B事务查询数据
- A事务修改数据已提交B事务增加数据
- A事务修改数据已提交B事务删除数据
- A事务修改数据已提交B事务修改数据
- A事务修改数据已提交B事务查询数据
- A事务查询数据已提交B事务增加数据
- A事务查询数据已提交B事务删除数据
- A事务查询数据已提交B事务修改数据
- A事务查询数据已提交B事务查询数据
B:A事务未提交情况
- A事务增加数据未提交B事务增加数据
- A事务增加数据未提交B事务删除数据
- A事务增加数据未提交B事务修改数据
- A事务增加数据未提交B事务查询数据
- A事务删除数据未提交B事务增加数据
- A事务删除数据未提交B事务删除数据
- A事务删除数据未提交B事务修改数据
- A事务删除数据未提交B事务查询数据
- A事务修改数据未提交B事务增加数据
- A事务修改数据未提交B事务删除数据
- A事务修改数据未提交B事务修改数据
- A事务修改数据未提交B事务查询数据
- A事务查询数据未提交B事务增加数据
- A事务查询数据未提交B事务删除数据
- A事务查询数据未提交B事务修改数据
- A事务查询数据未提交B事务查询数据
以上是A,B事务在并发情况下的所有组合,我们把以上异常情况做一个归纳,设计者们早就替我们总结了各自的概念含义,根据网上相关资料,能得到以下概念:
- 丢失更新:撤销一个事务时,把其他事务已提交的更新数据覆盖(A和B事务并发执行,A事务执行更新后,提交;B事务在A事务更新后,B事务结束前也做了对该行数据的更新操作,然后回滚,则两次更新操作都丢失了)。
- 脏读:一个事务读到另一个事务未提交的更新数据(A和B事务并发执行,B事务执行更新后,A事务查询B事务没有提交的数据,B事务回滚,则A事务得到的数据不是数据库中的真实数据。也就是脏数据,即和数据库中不一致的数据)。
- 不可重复读:一个事务读到另一个事务已提交的更新数据(A和B事务并发执行,A事务查询数据,然后B事务更新该数据,A再次查询该数据时,发现该数据变化了)。
- 覆盖更新:这是不可重复读中的特例,一个事务覆盖另一个事务已提交的更新数据(即A事务更新数据,然后B事务更新该数据,A事务查询发现自己更新的数据变了)。
- 虚读(幻读):一个事务读到另一个事务已提交的新插入的数据(A和B事务并发执行,A事务查询数据,B事务插入或者删除数据,A事务再次查询发现结果集中有以前没有的数据或者以前有的数据消失了)。
事务隔离级别的存在就是为了防止以上现象发生的,根据资料查证:
ANSI/ISO SQL定义的标准隔离级别如下:
可序列化(Serializable)
最高的隔离级别。
在基于锁机制并发控制的DBMS实现可序列化要求在选定对象上的读锁和写锁保持直到事务结束后才能释放。在SELECT 的查询中使用一个“WHERE”子句来描述一个范围时应该获得一个“范围锁(range-locks)”。这种机制可以避免“幻影读(phantom reads)”现象。
当采用不基于锁的并发控制时不用获取锁。但当系统探测到几个并发事务有“写冲突”的时候,只有其中一个是允许提交的。这种机制的详细描述见“‘快照隔离”
可重复读(Repeatable reads)
在可重复读(REPEATABLE READS)隔离级别中,基于锁机制并发控制的DBMS需要对选定对象的读锁(read locks)和写锁(write locks)一直保持到事务结束,但不要求“范围锁(range-locks)”,因此可能会发生“幻影读(phantom reads)”
提交读(Read committed)
在提交读(READ COMMITTED)级别中,基于锁机制并发控制的DBMS需要对选定对象的写锁(write locks)一直保持到事务结束,但是读锁(read locks)在SELECT操作完成后马上释放(因此“不可重复读”现象可能会发生,见下面描述)。和前一种隔离级别一样,也不要求“范围锁(range-locks)”。
简而言之,提交读这种隔离级别保证了读到的任何数据都是提交的数据,避免读到中间的未提交的数据,脏读(dirty reads)。但是不保证事务重新读的时候能读到相同的数据,因为在每次数据读完之后其他事务可以修改刚才读到的数据。
未提交读(Read uncommitted)
未提交读(READ UNCOMMITTED)是最低的隔离级别。允许脏读(dirty reads),事务可以看到其他事务“尚未提交”的修改。
通过比低一级的隔离级别要求更多的限制,高一级的级别提供更强的隔离性。标准允许事务运行在更强的事务隔离级别上。(如在可重复读(REPEATABLE READS)隔离级别上执行提交读(READ COMMITTED)的事务是没有问题的)
另外以上只是数据库层面的隔离级别,在一些应用层面也有针对以上情况定义出相应的隔离级别,例如Spring的隔离级别定义,大家有兴趣可以去查看相关资料。