跳转至

事务

https://www.liaoxuefeng.com/wiki/1177760294764384/1179611198786848

所谓的事务(transaction),就是把多条语句作为一个整体进行操作,这些操作要么全部成功,要么全部失败,是一个不可分割的集合。

事务具有 ACID 4 个特性:

AID 是实现 C 这个目标的手段

  • Atomicity 原子性,将所有SQL作为原子工作单元执行,要么全都执行,要么全不执行;
  • Consistency 一致性,事务完成后,所有数据的状态都是一致的,即 A 账户只要减去了100,B 账户则必定加上了 100;
  • Isolation 隔离性,如果有多个事务并发执行,每个事务作出的修改必须与其他事务隔离;
  • Durability 持久性,即事务完成后,对数据库数据的修改被持久化存储,不会再因为任何原因而导致其修改的内容被撤销或丢失。

单条 SQL 语句,其实也是作为一个事务被执行的,属于数据库系统默认的行为,这种事务被称为隐式事务

而通常我们所说的事务是显式事务

BEGIN;  -- 开启事务,或者 start transaction
UPDATE accounts SET balance = balance - 100 WHERE id = 1;  -- user1 的余额 -100
UPDATE accounts SET balance = balance + 100 WHERE id = 2;  -- user2 的余额 +100
COMMIT;  -- 提交事务,失败会回滚

-- 可以主动让事务失败回滚
ROLLBACK

有些客户端连接框架会默认连接成功后先执行一个 set autocommit=0 的命令,则不再需要 begin 显示启动事务,但这样会导致事务不再自动 commit,遇到长连接,就会一直处于同一个长事务中。

要避免使用长事务,除了占用锁资源,产生的回滚日志会占用大量存储空间。

-- 查找持续时间超过 60s 的事务
select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60

每条记录在更新的时候都会同时记录一条回滚操作,假设一个值从 1 被按顺序改成了 2、3、4,就会有类似下面的回滚日志记录

20241017230926

即同一条记录在系统中可以存在多个版本,也就是数据库的多版本并发控制(MVCC),可以将值从 4 依次回滚到 1。

当没有事务再需要用到这些回滚日志时,回滚日志才会被删除。

隔离级别

当数据库上有多个事务同时执行的时候,就可能出现脏读、不可重复读、幻读的问题,为了解决这些问题,就有了「隔离级别」的概念。

20241017220024

SQL 标准定义了 4 种隔离级别,不同隔离级别下 V1,2,3 读到的值是不一样的

读未提交 read uncommitted
事务 B 还没有提交时,变更结果就可以被事务 A 读到,所以 V1=V2=V3=2

读提交 read committed
事务 B 提交后,变更结果才可以被事务 A 读到,所以 V1=2, V2=V3=2

可重复读 repeatable read
事务在执行期间(即提交前)所看到的数据,前后总是一致的,所以 V1=V2=1, V3=2
另外,对其他事务也是不可见的。
适用场景:比如计算上个月余额和当前余额的差值,是否与本月的账单明细一致,这个过程中若不希望用户的新交易影响校对结果,就需要使用可重复读隔离级别。

串行化 serializable
读或写同一行记录时,会加读锁或写锁。当出现冲突时,后访问的事务必须等前一个事务执行完成,才能继续执行。
事务 B 将值从 1 修改到 2 时,会加写锁,作为后执行的事务,需要等待事务 A 提交后才可以继续执行。所以 V1=V2=1, V3=2

从视图的角度来理解不同隔离级别

读未提交,直接返回记录上的最新值,没有视图的概念
读提交,SQL 语句开始执行时创建视图
可重复读,事务启动时会创建视图,整个事务存在期间都用这个视图
串行化,使用加锁的方式避免并行访问

Oracle 默认的隔离级别是「读提交」,InnoDB 默认的隔离级别是「可重复读」,所以跨数据库迁移时一定要保证隔离级别一致

-- 查看当前事务隔离级别
SHOW VARIABLES LIKE 'transaction_isolation';
SHOW VARIABLES LIKE 'tx_isolation';  -- v8.0+
-- 查看全局事务隔离级别
SHOW GLOBAL VARIABLES LIKE 'tx_isolation';

-- 设置当前会话隔离级别
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 设置全局隔离级别
SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

不同隔离级别会产生的问题

20211103154025

  • 脏读 dirty read,一个事务会读到另一个事务未提交的更新,如果另一个事务回滚,那么当前事务读到的数据就是脏数据。
  • 不可重复读 non-repeatable read,在一个事务内,多次读同一数据,在这个事务还没有结束时,如果另一个事务恰好修改了这个数据,那么,在第一个事务中,两次读取的数据就可能不一致。
  • 幻读 phantom read,在一个事务中,第一次查询某条记录,发现没有,但是,当试图更新这条不存在的记录时,竟然能成功,并且,再次读取同一条记录,它就神奇地出现了。