事务和锁
约 1798 字大约 6 分钟
2025-01-15
1.事务
定义:逻辑上的一组操作,要么全部执行、要么全部不执行
事务的隔离级别,其中 MySQL 默认的为可重复读 RR
隔离级别 | 描述 | 问题 | 问题描述 |
---|---|---|---|
读未提交—RU | 一个事务可以读到其他事务没有提交的数据 | 脏读 | 一个事务读取到另一个事务尚未提交的数据,这些数据在事务提交前可能会被回滚,导致读取的数据不可靠。 |
读已提交—RC | 一个事务只能读到其他事务已经提交的数据 | 不可重复读 | 在一个事务中两次读取相同的数据,结果可能不同。因为其他事务在此期间可能已修改并提交了该数据。 |
可重复读—RR | 一个事务对数据的读取是可重复的,不变的 | 幻读 | 一个事务在查询结果集时,另一个事务可能插入、删除或更新数据,导致同一查询在同一事务中两次执行时返回不同的结果集 |
序列化 | 事务被完全串行化,事务执行时,其他事务无法访问已锁定的数据。 | 性能低 |
- 以下是事务的四大特性:
描述 | 实现 | |
---|---|---|
原子性 | 一组操作要么执行成功,要么执行失败 | undo log 回滚日志 |
一致性 | 数据的一致性,事务的最终目的 | |
隔离性 | 事务之间是隔离的 | 事务间的写操作靠MVCC机制(快照读、当前读)来保证隔离性 |
持久性 | 将数据保存至磁盘中 | redo log 重做日志 |
2.锁
2.1 锁分类
在MySQL中,锁是一种常见的并发事务的控制方式、用于保证数据一致性
- 锁的粒度:全局锁、表级锁、行级锁、页级锁
- 锁的级别:共享锁(读锁)、排他锁(写锁/独占锁)
- 锁的性能:乐观锁、悲观锁
- 锁的区间:间隙锁、临建锁
2.2 表级锁/行级锁
行级锁是指对数据库表中的某一行数据进行锁定,其他事务可以访问该表的其他行。
行级锁的分类:
共享锁(S锁):允许多个事务读取数据,但不允许修改:for lock in share mode
SELECT * FROM your_table WHERE id = 1 FOR LOCK IN SHARE MODE;
排他锁(X锁):允许一个事务对数据进行修改,其他事务不允许读取或修改:for update
SELECT * FROM your_table WHERE id = 1 FOR UPDATE;
表级锁则是对整个表进行锁定。当一个事务锁定了表,其他事务无法对该表进行任何操作
表级锁的分类:
- 写锁:LOCK TABLES your_table WRITE
- 读锁:LOCK TABLES your_table READ
2.3 乐观锁/悲观锁
乐观锁:认为数据在被操作时很少发生冲突,因此在访问数据时不会立即加锁,而是在更新数据时检查数据是否被其他事务修改过,如果没有则更新成功,否则进行回滚或者重试
举例:在MySQL中,可以使用乐观锁的方式是在更新数据时检查数据的版本号或者时间戳是否与当前操作一致
UPDATE table_name SET column1 = value1, version = new_version WHERE id = x AND version = old_version
悲观锁:认为数据在被操作时会发生冲突,因此在访问数据之前会先加锁,确保其他事务无法修改该数据,直到当前事务完成操作并释放锁。
举例:在MySQL中,可以使用SELECT ... FOR UPDATE语句来获取悲观锁,例如
SELECT * FROM table_name WHERE condition FOR UPDATE;
2.4 意向锁
- 意向锁(Intention Lock)是一种用于管理表级锁的锁机制,用于在表级别上指示事务将要在表的哪些行上获取锁
- 作用:在没有意向锁之前,如果一张表里面已经有行锁了,此时我们再添加表锁,为了防止表锁和行锁发生冲突,表锁就需要遍历整个表中的数据检查是否有行锁,效率低
- 分类:意向共享锁和意向排他锁
- 意向共享锁:表明事务将要在表的某些行上获取共享锁。当一个事务打算获取某行的共享锁时,会在表级别请求意向共享锁。
- 意向排他锁:表明事务将要在表的某些行上获取排他锁。当一个事务打算获取某行的排他锁时,会在表级别请求意向排他锁。
例子1(意向锁和表级的共享/排他锁互斥)
事务A | 事务B | |
---|---|---|
1 | 事务A成功获取第6行的排他锁,但未提交 | |
2 | 此时事务A持有该表的意向排它锁和第6行的排它锁 | |
3 | 事务B申请获取该表的读锁 | |
4 | 事务B检测到该表已有意向排它锁,因为意向锁和表级锁互斥,所以被阻塞 事务B不用去检测表中的每一行数据是否存在排他锁 |
例子2(意向锁和行级的共享锁/排他锁兼容)
事务A | 事务C | |
---|---|---|
1 | 事务A成功获取第6行的排他锁,但未提交 | |
2 | 此时事务A持有该表的意向排它锁和第6行的排它锁 | |
3 | 事务C申请获取第7行的排它锁 | |
4 | 事务C检测到该表有意向排它锁,但是意向锁和行级锁(排它锁/共享锁)兼容,即成功加锁 此时该表有事务A和事务C的两个意向排他锁,并获取到了第7行的排他锁 |
3.日志文件
redo log | undo log | bin log | |
---|---|---|---|
俗称 | 重做日志 | 回滚日志 | 二进制日志 |
内容 | 物理日志,记录事务中对数据的物理修改 | 逻辑日志,记录修改前的原始数据 | 记录所有执行增删改的SQL 语句,以及每个语句的执行时间 |
作用 | 确保事务持久性; 当事务提交时,MySQL会先将修改记录写入 redo log,并将其持久化到磁盘,即使数据库发生崩溃,可以通过 redo log 恢复已提交的事务 | 确保事务原子性; 用于数据回滚,同时可以提供多版本并发控制下的读(MVCC) | 主从复制 |
底层原理 | InnoDB存储引擎使用WAL(Write-Ahead Logging)机制,即先写日志,再写磁盘。每次事务提交时,InnoDB会先将Redo Log写入磁盘,然后再异步地将实际修改的数据写入磁盘 | InnoDB存储引擎会为每条记录维护一条Undo Log记录,并以链表的方式串联起来。如果事务需要回滚,MySQL会沿着Undo Log链表进行逐条回滚,直到恢复到事务开始时的状态 | MySQL在事务提交时将Binlog写入磁盘,确保数据的一致性和持久性。 |