美文网首页
【MySQL】3|事务隔离:为什么你改了我还看不见

【MySQL】3|事务隔离:为什么你改了我还看不见

作者: 学而思之 | 来源:发表于2022-01-07 09:15 被阅读0次

事务就是要保证一组数据库操作,要么全部成功,要么全部失败。

在MySQL中,事务支持是在引擎层实现的。MySQL支持多引擎,但并不是所有的引擎都支持事务。比如MySQL原生的 MyISAM 引擎就不支持事务,这也是MyISAM 被 InnoDB 取代的重要原因之一。

1、隔离性与隔离级别

提到事务,肯定会想到ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性),今天要说的是“I”,也就是“隔离性”。

当数据库上有多个事务同时执行的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题,为了解决这些问题,就有了“隔离级别”的概念。

SQL标准的事务隔离级别包括:

  • 读未提交(read uncommitted)

    一个事务还没提交时,它做的变更就能被别的事务看到

  • 读提交(read committed)

    一个事务提交之后,它做的变更才会被其他事务看到

  • 可重复读(repeatable read)

    一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。

  • 串行化(serializable)

    顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务务必须等前一个事务执行完成,才能继续执行。

其中“读提交”和“可重复读”比较难理解,所以我用一个例子说明这几种隔离级别。假设数据表T中只有一列,其中一行的值为1,下面是按照时间顺序执行两个事务的行为。

mysql> create table T(c int) engine = InnoDB;
insert into T(c) values(1);
事务A 事务B
启动事务
查询得到值1 启动事务
查询得到值1
将1改成2
查询得到值V1
提交事务B
查询得到值V2
提交事务A
查询得到值V3

在不同的隔离级别下,事务A会获得不同的返回结果:

  • 读未提交。V1、V2、V3值为2。虽然V1 时,事务B还未提交。
  • 读提交。V1为1,V2、V3值为2。事务B的更新在提交后才能被A看到。
  • 可重复读。V1、V2为1,V3为2。V2之所以还是1,遵循的是事务在执行期间看到的数据前后必须是一致的
  • 串行化。V1、V2为1,V3为2。事务B在执行“将1改成2”时,会被锁住。直到事务A提交之后,事务B才可以继续执行。

在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。

  • RR级别:视图是在事务启动时创建的,整个事务存在期间都用这个视图
  • RC级别:在每个SQL语句开始执行的时候创建的
  • RU级别:没有视图概念,直接读取记录上最新的值
  • Serial级别:直接用加锁的方式来避免并行访问,也没有视图概念

可以通过配置参数transaction-isolation来指定隔离级别,可以用show variables 来查看当前的值。

mysql> show variables like 'transaction_isolation';
+-----------------------+-----------------+
| Variable_name         | Value           |
+-----------------------+-----------------+
| transaction_isolation | REPEATABLE-READ |
+-----------------------+-----------------+
1 row in set (0.00 sec)

存在即合理,每种隔离级别都有自己使用的场景,需要根据具体业务情况来使用。

2、事务隔离的实现

以下是针对“RR-可重复读”隔离级别说明

在MySQL中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。

假设一个值从1被按顺序改成了2、3、4,在回滚日志里面就会有类似下面的记录。

undo逻辑

当前值是4,但是在查询这条记录的时候,不同时刻启动的事务会有不同的read-view。如图中看到的,在视图A、B、C里面,这一个记录的值分别是1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。对于read-view A,要得到1,就必须将当前值依次执行图中所有的回滚操作得到。

同时,你会发现即使现在有另外一个事务正在将4改成5,这个事务跟read-view A、B、C对应的事务是不会冲突的。

回滚日志在没有事务用到的时候会被删除,就是当系统里没有比这个回滚日志更早的read-view的时候。

基于上面的说明,建议不要使用长事务

长事务意味着系统里面会存在很老的事务视图。由于这个事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间。

除了对回滚段的影响,长事务还占用锁资源,也可能拖垮整个库,这个在后面锁的章节会展开说。

3、事务的启动方式

MySQL的事务启动方式有以下几种:

  1. 显式启动事务语句,beginstart transaction ,配套的提交语句是 commit,回滚语句是 rollback
  2. set autocommit = 0,这个命令会将这个线程的自动提交关掉。意味着哪怕你只执行一个 select 语句,这个事务就启动了,而且不会自动提交。这个事务会持续到你主动 commit 或者 rollback ,或者断开连接。

有些客户端连接框架会默认连接成功后先执行一个 set autocommit = 0 的命令。这就导致接下来的操作都在事务中,如果是长连接,就导致了意外的长事务。

因此,建议使用set autocommit = 1 ,通过显式语句的方式来启动事务。

可以在 information_schema 库的 innodb_trx 这个表中查询长事务,比如下面这个语句,用于查找持续时间超过 60s 的事务。

select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started))>60

相关文章

网友评论

      本文标题:【MySQL】3|事务隔离:为什么你改了我还看不见

      本文链接:https://www.haomeiwen.com/subject/ckfjcrtx.html