事务的并发控制是为了解决多个事务同时运行时可能出现的问题。主要的并发问题包括:
数据库通常通过锁定机制(如行锁、表锁、读/写锁等)和隔离级别(读未提交、读已提交、可重复读、序列化)来控制事务的并发,并尝试平衡并发性能与数据一致性之间的关系
在数据库中,不可重复读是一种并发控制问题,指的是在一个事务中多次读取同一数据时,得到的结果不一致。
下面是一个不可重复读的例子:
假设有一个银行账户表,其中包含了账户余额字段。假设有两个事务 T1 和 T2 同时进行。
在这个例子中,T1 事务在执行过程中,由于 T2 事务对账户 A 的余额进行了更新,导致 T1 事务两次读取的结果不一致,这就是不可重复读问题。
数据库事务的隔离级别定义了一个事务可能受其他并发事务影响的程度。SQL标准定义了四个隔离级别:
读未提交(Read Uncommitted):事务可以读取其他未提交事务的数据。这是最低的隔离级别,在这个级别,所有的并发问题如脏读、不可重复读和幻读都可能发生。
举例:如果事务A修改了一条记录但未提交,事务B可以读取这条“脏”的记录。如果事务A回滚,事务B读到的数据就是错误的。
读已提交(Read Committed):事务只能读取已经提交的事务的数据。这可以避免脏读。但是,不可重复读和幻读仍然可能发生。
举例:事务A读取了一条记录,这时事务B修改了这条记录并提交。当事务A再次读取同一条记录时,会得到一个不同的结果。
举个在一个事务执行期间多次读取同一数据的例子
当一个事务在执行期间多次读取同一数据时,在可重复读(Repeatable Read)隔离级别下,这些读取的结果都应该是一致的,不会发生不可重复读的情况。以下是一个例子:
假设有一个银行数据库,包含了客户的账户信息。在可重复读隔离级别下,如果一个事务正在执行,它可能如下操作:
事务开始:事务 A 开始执行,它想要查询自己的账户余额。
第一次读取:事务 A 查询自己的账户余额,并获得结果。
事务 B 干预:同时,事务 B 也在执行,它转账给事务 A 一定金额的钱,从事务 B 的账户扣款。
第二次读取:事务 A 再次查询自己的账户余额,以确认转账是否已经生效。
可重复读(Repeatable Read):在这个级别,事务在整个过程中可以多次读取同样的数据行,并且保证结果一致,避免了不可重复读。但是,幻读仍然可能发生。
举例:事务A读取满足某个条件的所有记录,这时事务B插入了一条新的满足条件的记录并提交。当事务A再次查询相同的条件,会发现有新的记录出现。
串行化(Serializable):这是最高的隔离级别,它通过锁定访问的数据行,来防止脏读、不可重复读和幻读,确保事务串行执行。
举例:如果事务A在执行查询时,事务B想要插入、更新或删除影响到事务A查询范围的数据,那么它必须等待事务A完成后才能执行。
不同的数据库系统可能会以不同的方式实现这些隔离级别。提高隔离级别可以减少并发事务问题,但同时可能会增加等待时间和锁争用,影响数据库性能。因此,在实际应用中,通常需要在数据的完整性和系统性能之间做出权衡。
在使用MySQL时,可能需要根据应用程序的具体需求设置事务隔离级别。可以通过SQL语句在会话或全局级别设置隔离级别,如下所示:
-- 设置当前会话的隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL [desired level];
-- 设置全局默认的隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL [desired level];
其中 [desired level] 可以是:
READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READSERIALIZABLEMySQL的默认事务隔离级别通常是REPEATABLE READ。在这个级别下,MySQL通过使用行级锁和一致性非锁定读取确保了可重复读,但在标准的SQL隔离级别中,幻读是可能的。然而,由于MySQL的InnoDB存储引擎使用了多版本并发控制(MVCC),在大多数情况下它实际上也可以防止幻读。
要查看当前MySQL会话或服务器的默认隔离级别,可以执行以下SQL查询:
-- 查看当前会话的隔离级别
SELECT @@tx_isolation;