1.会
2.取决于怎么定义phantom,以及有没有使用locking read。
MYSQL默认隔离级别按照SQL92的标准是REPEATABLE-READ,实际是SNAPSHOT。一些说法说SNAPSHOT可以避免phantom,也有一些说法说SNAPSHOT无法避免phantom。
其实这取决于怎么定义phantom,如果phantom专指【1】里面提到的anomaly,读到了不该读到的元组。 如果phantom也包括【2】里面提到的anomaly, 应该读到的元组没有读到。有些文章把为【2】里的anomaly都叫做write skew,不归类到phantom。
只需要记住,SNAPSHOT不会发生【1】提到的anomaly,但会发生【2】提到的anomaly。
例如,assignments表有四列(eid, pid, workdate, hours)。assignments表示的是给employee(eid)分配project(pid),并记录某个工作日(workdate)的工时(hours)。限制每个工作日工时不超过8小时。
assign表示分配工时的存储过程,假设eid为1的员工已有两个project, 工时分别为4,1。有两个并发的事务T1, T2, 同时执行assign。当T1,T2开始时,对于满足eid = 1 and workdate = '.3.1' 的元组,拿到的是相同的快照,它们都判定插入一条工时为4的元组不会使当日工时大于8。
如果把隔离级别改成SERIALIZABLE,就不会发生anomaly。因为在SERIALIZABLE隔离级别下会加谓词锁,并发控制算法变成了MV2PL。
create table assignments(
eid int ,
pid int,
workdate date,
hour int
);
insert into assignments values(1, 100, '.3.1', 4);
insert into assignments values(1, 101, '.3.1', 1);
delimiter //
create procedure assign(p_eid int, p_pid int, p_workdate date, p_hour int)
begin
declare v_hour int default 0;
select sum(hour) into v_hour from assignments where eid = p_eid and workdate = p_workdate;
if v_hour + p_hour <= 8 then
insert into assignments values(p_eid, p_pid, p_workdate, p_hour);
end if;
end//
delimiter ;
T1,T2执行前:
mysql> select * from assignments where eid = 1;
+------+------+------------+------+
| eid | pid | workdate | hour |
+------+------+------------+------+
| 1 | 100 | -03-01 | 4 |
| 1 | 101 | -03-01 | 1 |
+------+------+------------+------+
2 rows in set (0.00 sec)
事务T1:
set transaction_isolation='REPEATABLE-READ';
start transaction;
call assign(1, 102, '.3.1', 2);
commit;
事务T2:
set transaction_isolation='REPEATABLE-READ';
start transaction;
call assign(1, 103, '.3.1', 2);
commit;
T1,T2执行后:
mysql> select * from assignments where eid = 1;
+------+------+------------+------+
| eid | pid | workdate | hour |
+------+------+------------+------+
| 1 | 100 | -03-01 | 4 |
| 1 | 101 | -03-01 | 1 |
| 1 | 102 | -03-01 | 2 |
| 1 | 103 | -03-01 | 2 |
+------+------+------------+------+
4 rows in set (0.00 sec)
可以分析下这个例子里的异常是怎么产生的。T1,T2 并行的执行,事务开始,它们拿到的是相同的快照,都认为不会使当日工时大于8,于是都插入了一条记录。两个事务提交时,当日工时大于8了。
下面是更形式化的分析。T1用谓词P1读,T2插入了一条满足谓词P1的记录,所以存在一个T1到T2的rw(read write)依赖(T1应先于T2发生)。同时,也存在同样的T2到T1依赖。这样就有一个循环依赖,所以这个调度就不是串行化的,发生了异常。
判断并发执行的SQL会不会有异常,需要判断是否有循环的依赖。一个有用的技术是,找是否可能存在两个连续的rw依赖。因为在snapshot(MySQL的RR)隔离级别下,两个连续的rw依赖产生是发生不可串行化异常的必要不充分条件。具体细节和方法可以去参考论文【2】,论文最后用这个方法分析TPC-C为什么在snapshot下不会有异常。
还有,MySQL有一个比较特殊的情形,就是它允许覆盖更新这种行为(不遵守first commit win rule),这让它产生了另一种异常。
如下面的例子,有两个事务,在RR隔离级别下, select是没有幻读的,但select for update却会产生幻读。因为select是读,通过时间戳读快照,事务2读不到事务1的写入。而select for update被认为是写,是可以更新已提交数据的,所以读到的是最新版本,事务2可以读到事务1的写入。
事务1 事务2
mysql> start transaction; mysql> start transaction;
Query OK, 0 rows affected (0.00 sec) Query OK, 0 rows affected (0.00 sec)
mysql> select * from t;
Empty set (0.00 sec)
mysql> insert into t values(1);
Query OK, 1 row affected (0.00 sec)
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
// no phantom
mysql> select * from t;
Empty set (0.00 sec)
// phantom
mysql> select * from t for update;
+------+
| c |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
// update committed row
mysql> update t set c=2;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.01 sec)
参考:
【1】ESWARAN, K., GRAY, J., LORIE, R., AND TRAIGER, I. 1976. The notions of consistency and predicate locks in a database system. Commun. ACM 19, 11 (Nov.), 624–633
【2】Alan Fekete, Dimitrios Liarokapis, Elizabeth J. O'Neil, Patrick E. O'Neil, Dennis E. Shasha: Making snapshot isolation serializable. ACM Trans. Database Syst. 30(2): 492-528 ()
如果觉得《mysql mvcc 并发update_关于mysql的InnoDB多版本并发控制(MVCC)与事务隔离级别的疑问?...》对你有帮助,请点赞、收藏,并留下你的观点哦!