作者介绍李伟,现任金融行业核心业务系统DBA,主要参与DB2、Oracle、MySQL的开发和其他数据库。数据库的并发控制机制,顾名思义,就是用来控制数据库的并发操作的机制。控制的目的是保证数据的完整性和数据的一致性。什么是数据一致性?在数据库的并发操作中,多个事务同时读取同一个数据,需要保证多个事务读取的同一个数据是准确的。更好地理解可能的数据不一致。例如,交易T1和T2同时更新余额表为1000,T1减去100元,更新后的余额为900元,T2减去500元,更新后的余额为500元;当T2不知道T1更新后,直接更新余额为500元,T1更新的余额丢失,此时的数据不能反映真实情况,数据变得不一致。在并发事务处理过程中,事务T1可能会修改一条记录。事务T2读取记录后,事务T1被取消。此时T2读取的数据与数据库中的数据不一致,数据“脏”了。”数据。因此,在并发操作中,需要保证事务隔离,以保证数据的一致性。那么如何保证事务的隔离呢?接下来我们将对DB2、MySQL和Oracle的并发控制机制进行对比分析。数据库数据一致性支持机制:事务、锁、日志首先我们来看一下什么是事务1.Transaction事务:又称事务,是数据库系统访问的最小可恢复单元。1.ACID原子性(Atomicity)事务:事务是一个整体的工作单元,事务对数据库的操作不是执行就是取消;一致性(Consistency):事务完成后,所有数据保持一致的状态;隔离Isolation:所做的修改一个事务的修改必须与其他事务所做的修改隔离。当一个事务查看数据时,数据的状态要么在被其他事务修改之前,要么在被其他事务修改之后。交易,并且不会处于中间状态。即多个事务不能同时修改同一个数据;持久性:事务提交后,对数据库所做的更改将永久保存。2.事务的初始化和终止事务在可执行SQL第一次执行时自动初始化。事务一旦初始化,就必须终止(COMMIT或ROLLBACK)。1)关于事务的COMMIT和ROLLBACK,在大多数情况下,事务通过执行COMMIT或ROLLBACK来终止事务。COMMIT语句执行后,事务初始化后对数据库所做的所有更改都将永久化;ROLLBACK语句执行后,事务初始化后对数据库所做的所有修改都将被撤销,数据库恢复到事务开始前的状态。2)关于不成功事务的结果,当事务被COMMIT或ROLLBACK终止时会发生什么?如果系统在交易完成之前发生故障,会发生什么情况?在这种情况下,数据库管理器将撤消所有未提交的修改以恢复数据一致性。在DB2中,撤销修改是通过ACTIVELOG日志文件实现的。日志文件包含有关事务执行的每个语句的信息,以及事务是否成功提交或回滚。MySQL和Oracle使用撤消日志来撤消更改。undolog记录了行的修改操作。如果由于某种原因导致事务执行失败,或者使用ROLLBACK时,可以使用undolog将数据恢复到修改前的状态。3、事务隔离级别1)潜在问题为什么事务需要多个可以设置的隔离级别?通常,锁可以实现事务在并发操作中的隔离,保证数据的一致性。锁提高了并发性能,但会导致潜在的问题:脏读:当前事务可以读取另一个事务中未提交的数据。不可重复读:同一个事务读到的同一条数据是不同的。幻读:事务A在相同条件下第二次读取时读取的是新插入的数据。更新丢失:一个事务的更新操作会被另一个事务的更新操作覆盖,造成数据不一致。例如:事务T1修改行记录为V1,事务T1没有提交。事务T2修改行记录为V2,事务T2未提交。事务T1提交。事务T2提交。在目前数据库的锁机制下,理论上不会造成丢失更新的问题,但实际上,这个问题在所有的多用户计算机系统环境下都可能出现。例如:事务T1查询一行数据,放入本地内存,显示给User1。事务T2查询一行数据,放入本地内存,显示给User2。User1修改这行记录,更新数据库并提交。User2修改这行记录,更新数据库并提交。这些问题往往与系统数据库的使用和形式有关。设置事务的隔离级别就是根据不同的场景解决上述问题。比如上面提到的丢失更新问题,当隔离级别中的SELECT...FORUPDATE读取有更新意图时,步骤1和2都需要写,以避免丢失更新的问题。下面详细解释数据库的隔离级别及其加锁方式。2)数据库的隔离级别及其加锁方式①SQL标准定义的四种隔离级别READUNCOMMITTED:未提交读。事务可以看到其他事务拥有的未提交数据。不加锁读取数据;READCOMMITTED:提交阅读。事务只能看到其他事务提交的数据;可重复阅读:可重复性。锁定事务引用的部分满足检索条件的行。其他事务不能修改这些行,但可以执行INSERT操作。即可能会出现幻读;SERIALIZABLE:可序列化。强制排序,对每一个读取的数据行加锁,所有事务一个一个执行,事务之间不会有干扰。事务提交后释放锁。会导致大量超时和锁争用。②四种隔离级别带来的问题隔离级别脏读不可重复读幻读READUNCOMMITTED√√√READCOMMITTED×√√REPEATABLEREAD××√SERIALIZABLE××③数据库中的隔离级别DB2中的隔离级别:CS(CursorStability):光标稳定性。逐行锁定数据。当该行数据没有被修改时,释放锁,继续锁定下一行读取。当行数据被修改时,行锁会一直持续到事务终止。没有COMMIT,CS程序无法看到其他程序所做的更改。CS提供最大的并发性。但是如果在同一个事务中对同一个游标进行两次处理,可能会返回不同的结果,即不可重复性;当CS程序读取的行上存在任何可更新游标时,其他应用程序无法更新或删除该行。CS是DB2的默认隔离级别。当需要最大并行度但只能看到其他程序已提交的数据时使用。RR(RepeatableRead):可重复读。RR锁定事务引用的所有行,直到COMMIT。其他程序无法修改数据,如果同一条数据被访问两次,将返回相同的结果。RR是最高的隔离级别,最能保证数据的一致性,但是大量的加锁数据会导致并发度大大降低,并且可能会超过系统定义的锁持有数量限制。与标准定义的隔离级别中的SERIALIZABLE相比,锁定作用域是一样的。RS(ReadStability):读取稳定性。RS会在事务引用的所有行中锁定部分满足检索条件的行。其他程序不能修改,但是可以进行INSERT操作,所以在同一个事务中,如果两次访问数据,可能会返回新插入的数据,即幻读,但不会改变旧数据。与RR相比,RS锁定的数据数量大大减少,并发性提高。更适合在并发环境下运行,但只适用于不会在同一个事务中多次发出同一个查询,或者不需要同一个查询得到相同结果的程序,以免幻象阅读。DB2的RS类似于标准定义隔离级别中的REPEATABLEREAD(重复读),可以避免脏读,但是会出现幻读。UR(UncommittedRead):未提交读,即“脏”读。UR不加任何锁,可以读取数据库中的任何数据,包括已经修改但没有COMMIT的数据。读取的数据与真实数据之间可能存在一定的差距。UR级别最常用于对只读表的查询,或者当您只执行查询而不关心是否可以读取其他程序未提交的数据时。UR相当于标准定义的隔离级别中的READUNCOMMITTED(读未提交)。MySQL支持标准定义的四种隔离级别。默认的隔离级别是REPEATABLEREAD(重复),但是与标准SQL不同的是,MySQL的InnoDB存储引擎在REPEATABLEREAD的隔离级别下使用了Next-KeyLock(锁定)。一个范围,并锁定记录本身),从而避免幻读。因此,InnoDB存储引擎已经可以保证REPEATABLEREAD隔离级别下事务的隔离要求,即SQL标准的SERIALIZABLE隔离级别。Oracle数据库支持两种事务隔离级别,READCOMMITTED(提交读)和SERIALIZABLE。默认隔离级别是READCOMMITTED(已提交读取)。其次,锁事务隔离级别是并发控制的整体解决方案,实际上是利用各种类型的锁和行版本控制来解决并发问题。这里我们主要看一下数据库中的基本锁。1.锁型S-LOCK:共享锁。也叫读锁,当用户要读取数据时,在数据上加一个共享锁。可以同时添加多个共享锁;X-LOCK:独占锁。也称为写锁。当执行SQLINSERT/UPDATE/DELETE语句时,X-LOCK将被激活。只能加一个排他锁,与其他排他锁、共享锁互斥;U-LOCK:修改锁。当CURSORSELECT有UPDATEOF子句时,U-LOCK将应用于FETCH期间读取的记录。DB2、MySQL、Oracle都支持S-LOCK和X-LOCK,DB2也支持U-LOCK。2、事务隔离级别读取数据时的锁类型如上。在数据库各种隔离级别下,SQL执行INSERT/UPDATE/DELETE语句时都会加X-LOCK,那么读取数据时如何加锁呢?DB2和MySQL在UncommittedRead隔离级别下,没有加锁。1)当DB2DB2处于CS、RR、RS其他三个隔离级别时,SELECT语句,或者不带UPDATEOF子句的CURSORSELECT,都会对FETCH时读取的记录进行S-LOCK。不同的是,CS在读取一行数据时释放前一行的锁,而RR和RS在事务提交时释放锁;SELET...FORUPDATE对读取的数据加U锁,CS在读取下一行数据时释放上一行的锁,RR和RS只有在事务提交时才释放锁;X-LOCK是在执行INSERT/UPDATE/DELETE语句时发出的,CS、RR和RS只有在事务提交时才释放X锁,其他事务不能在锁定的行上加任何锁。2)MySQLMySQL的InnoDB在SELECT隔离级别READCOMMITED和REPEATABLEREAD(MySQL默认隔离级别)下不加锁,即MySQL中一致的非锁读;只指定SELECT...LOCKINSHAREMOAD记录S-LOCK,在SERIALIZABLE隔离级别下,SELECT记录S-LOCK;在三种隔离级别下,SELET...FORUPDATE对读数据加X锁,在MySQL中称为一致性锁读。3)OracleOracle只支持READCOMMITED和SERIALIZABLE隔离级别。这两种隔离级别下的锁机制与InnoDB是一致的。Oracle中不需要READUNCOMMITTED隔离级别,因为READUNCOMMITTED的主要作用是提高只读的并发,而Oracle在READCOMMITED隔离级别下使用一致性非锁读也有相同的作用。3、一致的非锁读隔离级别READCOMMITED和REPEATABLEREAD(MySQL默认的隔离级别)都使用了一致的非锁读,SELECT没有加锁,那么如何保证事务的隔离性呢?这两个隔离级别使用快照数据的方式来保证隔离。读取时,对于被X锁定的数据,会读取该行的快照数据。快照数据是指该行上一版本的数据,通过undo段实现。undo段用于回滚事务中的数据,因此快照数据本身没有额外的开销。读取快照数据时READCOMMITED和REPEATABLEREAD隔离级别的区别在于RC总是读取最新的快照数据,所以可能会出现不可重复读,即第二次读取的数据与第一次不一致;而RR总是在事务开始时读取快照,所以不会出现不可重复性。非锁读机制不等待释放行上的X锁,大大提高了数据库的并发性。这是InnoDB的默认读取模式。3.总结并发控制在保证数据一致性的前提下提供最大的并发性,而保证数据一致性的前提是保证事务的隔离性。事务的隔离性和并发性是成反比的。隔离级别越高,并发越低。因此,程序应该根据并发和隔离的严重程度来选择隔离级别。本文转载自微信公众号“DBAplus社区”,可通过以下二维码关注。转载本文请联系DBAplus社区公众号。
