MySQL 锁机制和事务(6)

一致读在某些DDL语句下不生效: 
碰到drop table语句时,由于InnoDB不能使用被drop的表,所以无法实现一致读 
碰到alter table语句时,也无法实现一致读

当碰到insert into… select, update … select和create table … select语句时,在默认的事务隔离级别下,语句的执行更类似于在读取提交数据的隔离级别下

加锁读操作

当在一个事务中在读操作结束后会执行insert和update操作时,普通的读操作无法阻止其他事务对相同数据执行修改操作,

所以InnoDB提供了两种在读操作时就增加锁的方式

select … lock in share mode:在读取的行数据上施加共享锁,其他的事务可以读相同的数据但无法修改;如果在执行此语句时有其他事务对相同的数据已经施加了锁,则需要等待事务完结释放锁

select … for update:和update操作一样,在涉及的行上施加排他锁,并阻止任何其他事务对涉及行上的修改操作、以及加锁读操作,但不会阻止对涉及行上的一般读(不加锁)操作

同样,锁的释放也是在事务提交或者回滚之后

比如在子表中插入一行数据,要确保对应的列在父表中有值,通过一般的读操作先查父表有值然后再插入的方法是不保险的,因为在读操作和插入操作之间就有可能其他事务会将父表的数据修改掉。那保险的做法是在查询父表是用加锁读的方式,比如: 
SELECT * FROM parent WHERE NAME = ‘Jones’ LOCK IN SHARE MODE;

再比如当表中有一个行数计数字段时,使用一致读和lock in share mode都有可能导致重复错误数据出现,因为有可能两个事务会读到相同的值,在这种情况下就要使用select … for update语句保证一个事务在读时,另一个事务必须等待

SQL语句对应的锁

加锁读,修改和删除SQL语句都会在索引扫描过的每一行增加锁,也就是说不光是在where条件限制的索引行上增加锁,也会对扫描到的间隔增加间隔锁

如果SQL语句是使用二级索引查找数据而且施加的是排他锁,则InnoDB也会在对应的聚簇索引行上施加锁

如果SQL语句没有任何索引可以使用,则MySQL需要扫描全表数据,而每 
行数据都会被施加锁,所以一个良好的习惯是为InnoDB添加合适的索引

针对不同的语句, InnoDB会施加不同的锁: 
Select…from语句属于一致性读, 在默认情况下不施加任何的锁,除非在可串行化隔离级别下,会施加共享next-key锁在扫描的索引行上,当碰到使用唯一索引查找唯一值时只在唯一值上施加锁

Select…lock in share mode语句会在索引扫描行上施加共享next-key锁,除非是当碰到使用唯一索引查找唯一值时只在唯一值上施加锁

Select…for update语句会对扫描索引的行上施加排他next-key锁,除非是当碰到使用唯一索引查找唯一值时只在唯一值上施加锁

Update语句会对扫描索引的行上施加排他next-key锁,除非是当碰到使用唯一索引查找唯一值时只在唯一值上施加锁。

Delete语句会对扫描索引的行上施加排他next-key锁,除非是当碰到使用唯一索引查找唯一值时只在唯一值上施加锁

Insert语句会对索引扫描的行上施加锁,但不是next-key锁,所以不会阻止其他事务对该行值前的间隔上插入数据

Insert into T select…from S语句会对插入到T表的行施加排他锁(非间隔锁), 而在默认隔离级别下会对访问的S表上的行施加共享next-key锁

当表上有外键约束时,对任何的insert,update和delete操作都会在需要检查外键约束的行上施加共享行锁

Lock table语句是施加表级锁

幻读

幻读问题发生在同一个事务中当相同的读操作在前后两次读数据时返回不同的结果集。 
比如在表的ID字段上有一个索引,当希望对ID>100的数据进行后续修改时,我们会使用如下的语句: SELECT * FROM child WHERE id > 100 FOR UPDATE,而如果表里目前只有90和102两个值时,如果没有间隔锁锁住90到102之间的间隔,则其他的事务会插入比如101这个值,这样的话在第二次读数据时就会返回三行记录而导致幻读

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/6c657295ebb4837b7471e8c9aab82ec3.html