数据库的事务隔离级别与锁机制
一、为什么要事务隔离
事务是一个具有ACID特性的程序执行单元,多个线程下的多个事务对同一数据库表进行操作时,将会涉及并发性与数据一致性的问题。以下将以事务A、事务B来举例说明。
不设置事务隔离会造成哪些数据不一致的错误,按照错误级别从低到高分为脏读、不可重复读、幻读。
- 脏读
事务A更新了一份数据,此时事务B读取了这个数据,如果因为某些原因事务A回滚了,则事务B读取到的数据将是不正确的数据(脏数据),即为脏读。
在错误级别上,事务A写入了脏数据,属于最低级别的错误。
- 不可重复读
事务B在一个事务内多次读取了同一数据,如若事务A在事务B两次读取数据之间修改了此数据,并且提交了此修改,那么事务B前后两次读取到的数据是不一致的,即为不可重复读。
在错误级别上,事务A提交了正确数据(涉及的操作可能是update或delete),是比脏读高一级别的错误。
- 幻读
事务B在一个事务内多次读取数据条数,如若事务A在事务B两次读取数据之间插入了几条数据,并且提交了此事务,那么事务B前后两次读取到的数据条数是不一致的,即为幻读。
在错误级别上,事务A提交了正确数据(涉及的操作是insert),是比脏读和不可重复读更高一级别的错误。
不可重复读与幻读的区别:
两者很相似,可结合下文锁机制和事务隔离级别来理解其中的区别,不可重复读涉及的操作是update或delete,幻读涉及的操作是insert
二、四种事务隔离级别
数据库的四种事务隔离级别可依次分别解决上述的三种数据不一致的错误,由低到高分别为:
- 读未提交(Read Uncommitted)
该隔离级别,其实是对多个事务操作同一数据库表的情况下不做任何控制,所以在此事务隔离级别上述三种数据不一致的错误都可能出现。该级别隔离有最高的并发性能,也会有大量数据不一致的错误,很少用于实际应用。
- 读已提交(Read Committed)
该隔离级别,其实是对多个事务操作同一数据库表的情况下做了一点的控制:一个事务只能看到已提交事务所做的改变。所以在此事务隔离级别可避免“脏读”的错误,其他两种数据不一致的错误还是可能出现。 这是大多数数据库默认的隔离级别如oracle。
- 可重复读(Repeatable Read)
该隔离级别,其实是对多个事务操作同一数据库表的情况下做了更多的控制:它确保同一事务的多个实例在并发读取数据时,目标数据行不会被修改。所以在此事务隔离级别可避免“脏读”和“不可重复读”的错误,而幻读的错误还是可能出现。这是MySQL数据库默认的隔离级别,而幻读的错误可通过更高的隔离级别或者多版本并发控制机制来解决。
- 可串行化(Serializable)
该隔离级别,其实是对多个事务操作同一数据库表的情况下做了最高级别的控制:它通过强制事务排序,使之不可能相互冲突。所以在此事务隔离级别可避免“脏读”、“不可重复读”和“幻读”的错误。该隔离级别保证了数据一致性,但也会导致大量的超时现象和锁竞争。
四种隔离级别可解决的错误列表如下
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
读未提交 | |||
读已提交 | √ | ||
可重复读 | √ | √ | |
可串行化 | √ | √ | √ |
三、事务隔离原理之锁机制
隔离级别对多个事务操作同一数据库表时所做控制的原理本质上是锁机制,而数据库的锁机制和多版本并发控制等一系列内容较多,在此不展开说明,能够解释隔离原理就好。
- 读未提交
该隔离级别,其实是使用了写锁(行级共享锁),所以可以读到其他事务未提交的数据。
- 读已提交(Read Committed)
该隔离级别,其实是使用了写锁(行级排他锁),一个事务更新数据时,在commit提交或者rollback回退之前,不允许其他事务对更新的数据写和读(即事务完成之前不释放锁)。所以不会读到其他事务提交的脏数据,避免脏读。还使用了读锁(行级共享锁),读完就释放锁,所以不解决不可重复读的问题。
- 可重复读(Repeatable Read)
该隔离级别,其实是在使用了写锁(行级排他锁)的同时,又使用了读锁(行级共享锁)且在事务完成前一直持有锁,所以不会出现同一事务中读取到前后不一致数据的情况,又避免了不可重复读。
为何不能解决幻读的错误?由于此读锁是行锁,所以在可重复读中第一次读取数据时,对所查出的数据行加锁,使其他事务不能对这些数据行操作update或delete,但此时还是可以insert操作,所以无法解决幻读的错误,若解决幻读需要读锁是表锁。
- 可串行化(Serializable)
该隔离级别,其实是使用了写锁(表级排他锁)和读锁(表级共享锁),使强制事务排序,使之不可能相互冲突。