MySQL 事务隔离

事务就是保证一组数据库操作,要么全部成功,要么全部失败。在MySQL中事务是在引擎层实现的。

一、事务特性:

ACID:原子性、一致性、隔离性、持久性

  1. 原子性:事务中全部操作,要么全部完成,要么全部失败;
  2. 一致性:几个并行事务,执行结果必须与按某一顺序串执行结果相一致;
  3. 隔离性:事务的执行不受其他事务干扰,事务执行的中间结果对其他事务是透明的;
  4. 持久性:任意提交的事务,系统必须保证该事务对数据库的改变不被丢失;

二、隔离性与隔离级别:

当数据库上有多个事务同时执行,可能出现脏读、不可重复读、幻读问题,为了解决这些问题,就有了隔离级别。隔离得越严实,效率就会越低。

事务的隔离级别包括:

  • 读未提交:一个事务还没有提交,它做的变更会被其他事务看到;
  • 读提交:一个事务提交后,它做的变更会被其他事务看到;
  • 可重复读:一个事务在执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的;

未提交变更对其他事务也不可变(InnoDB引擎默认)

  • 串行化:同一记录,写会加写锁。读会加读锁。当出现读写冲突的时,后访问的事务必须等前一个事务执行完成,才能继续执行;

数据库里会创建一个视图,访问的时候会以视图的逻辑为准:

  • 读未提交:隔离级别下,直接返回记录上的最新值,没有视图概念;
  • 读提交:隔离级别下,视图在每个SQL语句开始执行时创建;
  • 可重复读:隔离级别下,视图是在事务启动的时候创建,整个期间都在使用该视图;
  • 串行化:隔离级别下,直接使用加锁方式来避免访问;

注意

  • Oracle 数据库默认隔离级别,就是读提交,若要进行迁移MySQL,需要注意隔离级别:
  • transaction-isolation 的值设置成 READ-COMMITED:
# 查看当前值
mysql> show variables like 'transaction_isolation';

+-----------------------+----------------+

| Variable_name | Value |

+-----------------------+----------------+

| transaction_isolation | READ-COMMITTED |

+-----------------------+----------------+

InnoDB支持RC(读提交)和RR(可重复读)隔离级别实现是用的一致性视图(consistent read view)。

三、事务隔离级别的实现:

以下所说的是,可重复读隔离级别的实现。

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

同一条记录在系统中可存在多个版本,就是数据库的多版本并发控制(MVCC)。

为什么不使用长事务?

  • 会对回滚有影响,长事务还占用锁资源。

四、事务启动的方式:

  1. 显示启动事务语句,begin 或 start transaction;提交语句 commit;回滚语句rollback;
  2. set autocommit=0,这个命令会将这个线程自动提交关掉。

仅执行一个select语句,事务就已经开启,并不会自动提交。这个事务持续存在,直到主动执行commit 或 rollback语句,或断开连接。

  1. 提交事务并启动下一个事务:commit work and chain
  2. 在information_schema库的innodb_trx表中查询长事务。下面语句,查找持久时间超过60s的事务;

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

五、快照:

事务在启动时会拍一个快照,这个快照是基于整个库的。

  • 基于整个库的意思,就是说一个事务内,整个库的修改对该事务都是不可见的(对于快照读情况),如果在事务内select T表,另外的事务执行了DDL(修改字段) T表,根据发生的事件,要不就锁住,要不就报错。

六、什么是MVCC呢?

  1. MVCC即多版本并发控制。
  2. MySQL大多数是事务型存储引擎实现的都不是简单的行级锁。基于提升并发性能的考虑,它们一般都同时实现了MVCC。

MVCC优缺点:

  • 优点:MVCC大多数情况下代替了行锁,实现了对读的非阻塞,读不加锁,读写不冲突。
  • 缺点:每行记录都需要额外的存储空间,需要做更多的行维护和检查工作。

事务如何实现MVCC呢?

1.每个事务都有一个事务ID,叫做transaction id(严格递增)。

2.事务在启动时,找到已经提交的最大事务ID标记为up_limit_id。

3.事务在更新一条语句时,比如id=1改为id=2,会把id=1的行之前的orw trx_id 写到undo log里,并在数据页上把id的值改为2,并且把修改这条语句的transaction id 记在该行行头。

4.在定一个规矩,一个事务要查看一条数据时,必须先用该事务的up_limit_id与该行的transaction id 做比对:

up_limit_id >= transaction id,那么可以看;

up_limit_id < transaction id ,只能去undo log里面取;

去undo log查找数据的时候,也需要对比,必须up_limit_id > transaction id 才返回数据;

什么是当前读?

  • 由于当前读都是先读后写,只能读当前的值,所以当前读,会更新事务内的up_limit_id为该事务的transaction id;