什么是事务?
事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务的四大特性
原子性(Atomicity)
原子性意味着事务中对数据库的一系列操作要么全部成功,要么全部失败,不可能出现部分成功或部分失败的情况。以转账为例,一个账户的余额减少,另一个账户的余额增加,这两个操作必须同时成功或同时失败。如果前一个操作成功了,而后一个操作失败,则必须回滚以确保原子性。原子性在InnoDB中是通过undo log
来实现的,它记录了数据修改之前的值,以便在异常发生时通过undo log
进行回滚操作。
一致性(Consistency)
一致性指的是事务执行前后数据库的状态必须是合法的,数据库的完整性约束没有被破坏。除了数据库自身的完整性约束,还包括用户自定义的完整性约束。例如,主键必须唯一,字段长度必须符合要求。在转账场景中,如果A账户减少1000元而B账户只增加500元,即使操作成功,也不满足一致性,因为导致了会计科目不平衡。
隔离性(Isolation)
隔离性确保多个事务并发执行时,一个事务的执行不应影响其他事务。不同事务对同一数据的并发操作应该是相互隔离的,透明的,互不干扰。隔离性通过多版本并发控制(MVCC)和锁机制来实现。
持久性(Durability)
持久性保证了事务一旦提交,其对数据库的修改将永久保存在数据库中,即使系统发生崩溃也不会丢失。持久性通过redo log
实现,数据修改会先写入内存的缓冲池,并记录在redo log
中,即使系统崩溃也能通过redo log
恢复数据。
事务并发带来的问题
当多个事务并发操作数据库时,会产生脏读、不可重复读和幻读等问题。
脏读
一个事务读取了另一个事务未提交的修改数据,可能导致读取的数据不一致或不准确。
不可重复读
一个事务内多次读取同一数据,在这个事务未结束时,另一个事务修改了该数据,导致前后读取结果不一致。
幻读
一个事务读取某一范围的数据时,另一个事务在该范围插入新数据,导致前后读取的结果不一致,出现“幻觉”。
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(255) DEFAULT NULL,`age` int(11) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB;INSERT into user VALUES (1,'1',20),(5,'5',20),(15,'15',30),(20,'20',30);
假设有如下业务场景:
时间 | 事务1 | 事务2 |
---|---|---|
begin; | ||
T1 | select * from user where age = 20;2个结果 | |
T2 | insert into user values(25,‘25’,20);commit; | |
T3 | select * from user where age =20;2个结果 | |
T4 | update user set name=‘00’ where age =20;此时看到影响的行数为3 | |
T5 | select * from user where age =20;三个结果 |
隔离级别
隔离级别是事务并发控制的等级,描述事务之间的隔离程度。SQL标准定义了四个隔离级别:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
READ-UNCOMMITTED(读取未提交):允许读取未提交的数据,可能会出现脏读。
READ-COMMITTED(读取已提交):只能读取已提交的数据,可能会出现不可重复读。
REPEATABLE-READ(可重复读):保证多次读取同一数据的一致性,但可能会出现幻读。
SERIALIZABLE(可串行化):最高隔离级别,完全隔离,防止脏读、不可重复读和幻读。
解决读一致性问题
LBCC(基于锁的并发控制)
通过锁定操作的数据行,确保事务读取的数据一致性。这种方式可能导致性能下降,因为阻止了并发读写操作。
MVCC(多版本并发控制)
通过保存数据的多个版本,实现事务之间的并发控制,确保读一致性,同时提高并发性能。
InnoDB中的锁
https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html
共享锁
允许事务读取数据,多个事务可以共享一个读锁。使用SELECT ... LOCK IN SHARE MODE
语句手工加锁。
排它锁
用于修改数据,事务获取排它锁后,其他事务不能再获取该行的共享锁和排它锁。使用FOR UPDATE
语句手工加锁。
意向锁
数据库自动维护,用于标识表中的某些数据行被加锁。
记录锁
锁定记录行所在的索引。
间隙锁
锁定索引记录之间的间隙,用于防止幻读。
临键锁
记录锁和间隙锁的组合,锁定索引记录及其前后的间隙。
死锁及其解决方案
死锁是指两个或多个事务相互等待对方释放锁,从而无法继续执行的现象。解决死锁的方法包括:
- 按照相同顺序访问表,减少死锁概率。
- 尽可能在事务中一次锁定所有资源。
- 对容易产生死锁的部分,尝试使用表锁。
- 优化索引,减少死锁可能性。
可以参考以下文章了解更多死锁分析和解决方案:
死锁分析与解决1
死锁分析与解决2