事务基本概念
事务特性
ACID(Atomicity、Consistency、Isolation、Durability,即原子性、一致性、隔离性、持久性)
问题
脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)
隔离级别
- 读未提交是指,一个事务还没提交时,它做的变更就能被别的事务看到。
- 读提交是指,一个事务提交之后,它做的变更才会被其他事务看到。
- 可重复读是指,一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
-
串行化,顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
image.png
在不同隔离级别下,V1、V2、V3分别是多少
事务隔离的实现
在实现上,数据库里面会创建一个视图,访问的时候以视图的逻辑结果为准。在“可重复读”隔离级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。在“读提交”隔离级别下,这个视图是在每个 SQL 语句开始执行的时候创建的。这里需要注意的是,“读未提交”隔离级别下直接返回记录上的最新值,没有视图概念;而“串行化”隔离级别下直接用加锁的方式来避免并行访问。
通过多版本并发控制MVCC实现
image.png
通过undolog,可以实现获取指定版本的数据。InnoDB 利用了“所有数据都有多个版本”的这个特性,实现了“秒级创建快照”的能力。
为什么尽量不要使用长事务。
长事务意味着系统里面会存在很老的事务视图。由于这些事务随时可能访问数据库里面的任何数据,所以这个事务提交之前,数据库里面它可能用到的回滚记录都必须保留,这就会导致大量占用存储空间;又因为指定版本的数据,是通过最新版本然后倒序执行所有undolog获取到的,所以长事务的select语句可能耗时很久
举例1
可重复读隔离级别
image.png
image.png
一个数据版本,对于一个事务视图来说,除了自己的更新总是可见以外,有三种情况:
- 版本未提交,不可见;
- 版本已提交,但是是在视图创建后提交的,不可见;
- 版本已提交,而且是在视图创建前提交的,可见。
若改为当前读
image.png
更新数据都是先读后写的,而这个读,只能读当前的值,称为“当前读”(current read)。select 语句如果加锁,也是当前读。所以,如果把事务 A 的查询语句 select * from t where id=1 修改一下,加上 lock in share mode 或 for update。
假设事务 C 不是马上提交的,而是变成了下面的事务 C’,会怎么样呢
image.png
事务 C’没提交,也就是说 (1,2) 这个版本上的写锁还没释放。而事务 B 是当前读,必须要读最新版本,而且必须加锁,因此就被阻塞了,必须等到事务 C’释放这个锁,才能继续它的当前读。
假如隔离级别是读提交,A读到的又是什么呢?
举例2
当前库存:num=200
假如多线程并发,AB同时开启事务,A先请求到行锁,
A:
start transaction;
select num from t where num>0;先查询当前库存值(num>0)
update t set num=num-200; 库存减量
B:
start transaction;
select num from t where num>0;先查询当前库存值(num>0)
update t set num=num-200; 库存减量
----结果---
A:查询到num=200,做了库存减量成了0
B:事务启动后,查询到也是200,等 A 释放了行锁,B进行update,直接变成 -200
但是 B 查询时,时有库存的,因此才减库存,结果变成负的。
-
问题分析
问题主要是快照读与当前读数据不一致导致的 -
如何解决
查询采用当前读;或者更新时加where条件,要求库存大于200
网友评论