002MySQL学习014锁机制和MVCC机制

002MySQL学习014锁机制和MVCC机制,第1张

002MySQL学习014锁机制和MVCC机制 1 锁机制 1.1 概述

MySQL的锁机制,就是数据库为了保证数据的一致性而设计的面对并发场景的一种规则。

不同的存储引擎支持不同的锁机制,InnoDB支持行锁和表锁,MyISAM支持表锁。

1.2 锁分类 1.2.1 共享锁和排他锁

按锁的互斥程度来分,可以分为共享锁和排他锁:

共享锁:多个读 *** 作可以同时进行,其他写 *** 作会被阻断。

排他锁:其他读 *** 作和写 *** 作均会被阻断。

1.2.2 表锁和行锁

按锁的粒度来分,可以分为表锁和行锁:

表锁:粒度最大的锁,表示当前 *** 作对整张表加锁,资源开销小,不会出现死锁,锁冲突概率大。InnoDB和MyISAM都支持。

行锁:粒度最小的锁,表示当前 *** 作对 *** 作的行加锁,资源开销大,会出现死锁,锁冲突概率小。只有InnoDB支持。

2 锁细说 2.1 共享锁和排他锁 2.1.1 共享锁

共享锁(Share Locks),记为S锁,表锁,InnoDB和MyISAM都有。

读取数据时如果表上没有锁,则在表上加读锁,否则进入读锁队列。

2.1.2 排他锁

排他锁(Exclusive Locks),记为X锁,表锁,InnoDB和MyISAM都有。

更新数据时如果表上没有锁,则在表上加写锁,否则进入写锁队列。

2.1.3 优先级

当锁被释放时,优先执行写锁队列中的请求,写锁队列为空时,执行读锁队列中的请求。所以,如果有大量更新 *** 作,可能会导致查询 *** 作很难获得锁,从而长久阻塞,使程序响应超时。

2.1.4 加锁

对于更新语句,MySQL会自动加X锁,对于查询语句,MySQL不会自动加S锁。

当前线程给当前表加S锁,当前线程只能读取当前表,当前线程更新当前表失败,当前线程查询其他表失败,其他线程只能读取当前表,其他线程更新当前表阻塞。

当前线程给当前表加X锁,当前线程可以读取和更新当前表,当前线程查询其他表失败,其他线程读取当前表阻塞。

查看表上的锁:

show open tables;

在执行查询语句时,会自动给表加读锁,也可以显示加锁:

lock tables 表名 read;

在执行更新语句是,会自动给表加写锁,也可以显示加锁:

lock tables 表名 write;

释放锁:

unlock tables;
2.2 意向锁 2.2.1 说明

InnoDB为了支持多粒度锁机制,即为了支持表锁和行锁共存,引入了意向锁(Intention Locks)。

意向锁指的是在未来某个时刻,事务可能要加锁,先提前声明一个意向。

意向锁产生的目的主要是为了处理行锁和表锁之间的冲突,事务在请求S锁和X锁之前,需要先获得对应的IS锁和IX锁,在加S锁和X锁之前,会先加IS和IX锁。

使用意向锁提高了在多粒度锁并存时判断某行数据是否存在行锁的性能:

在没有使用意向锁的情况下,使用S锁或者X锁对某行加锁,其他线程在使用S锁或者X锁加表锁时,需要判断每行数据是否有锁。

在使用意向锁的情况下,使用S锁或者X锁对某行加锁,先使用对应的IS锁或者IX锁加表锁,其他线程在使用S锁或者X锁加表锁时,通过判断IS锁和IX锁即可,不需要判断每行数据。

2.2.2 意向共享锁

意向共享锁(Intention Share Locks),记为IS锁,表锁,MyISAM没有。

表示事务有意向对表中的某行加共享S锁。

2.2.3 意向排他锁

意向排他锁(Intention Exclusive Lock),记为IX锁,表锁,MyISAM没有。

表示事务有意向对表中的某行加排他X锁。

2.2.4 加锁

当前线程加IS锁并且给当前行加行锁,其他线程可以加IS锁并且给当前行加行锁,其他线程可以加IX锁并且给其他行加行锁,其他线程可以加S锁,其他线程不能加X锁。

当前线程加IX锁并且给当前行加行锁,其他线程可以加IS锁并且给当前行加行锁,其他线程可以加IX锁并且给其他行加行锁,其他线程不能加S锁,其他线程不能加X锁。

查询时加意向共享锁:

select * from 表名 lock in share mode;

查询时加意向排他锁:

select * from 表名 for update;
2.2.5 注意

使用意向锁必须开启事务,并且在begin之后,commit和rollback之前才会生效。

InnoDB行锁是针对索引加的意向锁,不是针对记录加的意向锁。

意向锁在某些情况下不会加行锁:

1)查询语句没有使用主键索引并且没有使用辅助索引,此时会使用表锁。

2)查询语句使用了范围条件导致索引无效,此时会使用表锁。

3)查询语句使用主键索引或者使用辅助索引,执行的结果没有数据,此时不会使用任何锁。

2.3 记录锁

记录锁(Record Locks),行锁,索引记录锁,建立在索引记录上的锁,为某行记录加锁,索引基于唯一索引(包括主键索引)。

查询时加共享记录锁:

select * from 表名 where id = 1 lock in share mode;

查询时加排他记录锁:

select * from 表名 where id = 1 for update;
2.4 间隙锁

间隙锁(Gap Locks),行锁,索引区间锁,建立在索引区间上的锁,为某个开区间加锁,开区间不包括端点。

使用间隙锁可以防止幻读,保证索引区间上的数据不会被更改。

需要注意的是,如果把MySQL的事务隔离级别降级为读已提交(Read Committed,RC),间隙锁就会自动失效。

查询时加共享间隙锁:

select * from 表名 where id > 1 lock in share mode;

查询时加排他间隙锁:

select * from 表名 where id > 1 for update;
2.5 临键锁

临键锁(Next-key Locks),行锁,记录锁和间隙锁的组合,索引区间是一个开区间,索引基于非唯一索引。

每个数据行都可以看做一个记录锁,数据行上下相邻两边的间隙可以看做两个间隙锁。

使用临建锁也可以防止幻读,保证索引区间上的数据不会被更改。

需要注意的是,如果把MySQL的事务隔离级别降级为读已提交(Read Committed,RC),临键锁就会自动失效。

临键锁在使用唯一索引的不同场景中会变化:

1)使用唯一索引,使用精确匹配,记录存在,变化为记录锁。

2)使用唯一索引,使用精确匹配,记录不存在,变化为间隙锁。

3)使用唯一索引,使用范围匹配,变化为临键锁,左开右闭。

4)使用无索引,变化为表锁。

2.6 插入意向锁

插入意向锁(Insert Intention Locks),行锁,特殊的间隙锁,多事务在同一个索引列上的同一个间隙中插入不同行的记录不会产生冲突。

多个事务同时在同一个间隙中插入记录时,获取各自的插入意向锁,获取在插入行上的排他锁,不同行的插入不会冲突。

2.7 自增锁

自增锁(Auto-inc Locks),表锁,特殊的表锁,发生在AUTO_INCREMENT主键自增的列,多事务同时插入时会阻塞,保证主键的连续。

3 MVCC机制 3.1 是什么

MVCC(Multi-Version Concurrency Control),多版本并发控制,是保证事务隔离性的机制。

最早的数据库系统只有读读支持并发,读写、写读、写写都会被阻塞,在引入MVCC机制后,只有写写会被阻塞,读读、读写、写读都支持并发。

不同的数据库引擎对MVCC机制有不同的实现,本文只学习MySQL数据库InnoDB引擎对MVCC机制的实现。

在RC读已提交中,MVCC机制解决了写读的并发问题,即解决了脏读的问题。在RR可重复读中,MVCC机制解决了读写的并发问题,即解决了不可重复读的问题。

注意,MVCC机制不能解决更新丢失的问题,但锁机制解决了这个问题,锁机制通过加锁保证写写不会并发。

4 MVCC原理 4.1 概述

在InnoDB引擎中,MVCC机制的实现主要靠表中的三个隐藏字段,undo日志,read视图实现的。

当前事务在执行查询语句时,会生成read视图,根据read视图判断查询当前表还是查询undo日志,不同的事务生成不同的read视图,可能查询当前表也可能查询undo日志。

把通过undo日志进行的查询 *** 作称为快照读,无锁的查询 *** 作有可能会执行快照读。把通过当前表进行的查询 *** 作称为当前读,有锁的 *** 作都会执行当前读。

4.2 概念 4.2.1 隐藏字段

在创建表时,除了创建定义的字段,还会默认创建三个隐藏字段:

trx_id:事务ID,记录最近修改和插入这条记录的事务ID。

roll_pointer:回滚指针,记录最近修改这条记录的上一版本,通过这个字段可以在undo日志中找到最近的历史版本。

row_id:隐藏主键,如果数据表没有主键,InnoDB会在这个字段创建聚簇索引。

其实还有删除标志字段,表示有事务将该记录删除了,事务提交前只是修改这个字段,事务提交后才会删除记录。

4.2.2 undo日志

undo日志可以看做一个版本链,用于保存修改和插入的历史版本,节点可以看做表中记录的拷贝,通过roll_pointer字段行成链式结构。

在执行快照读时,会根据roll_pointer遍历链表,直到找到符合要求的历史版本。

4.2.3 read视图

read视图可以看做一个事务快照,由当前系统中活跃的事务ID组成一个快照列表。

因为事务ID是按先后顺序生成的,所以需要比较要查询的记录的trx_id字段上的事务ID和read视图中的事务ID即可判断数据的来源:

1)如果trx_id小于read视图的最小值,说明最近更新这条记录的事务发生在过去已提交,执行当前读。

2)如果trx_id在read视图范围中,需要进一步判断。如果在read视图中能找到trx_id则表示事务未提交,执行快照读,如果找不到则表示事务已提交,执行当前读。

3)如果trx_id大于read视图的最小值,说明最近更新这条记录的事务发生在未来未提交,执行快照读。

4.3 结合 *** 作举例说明 4.3.1 查询 *** 作

执行查询 *** 作时,生成read视图,对比数据行上的trx_id字段,进行当前读或者快照读。

在快照读时,如果找到的历史版本的删除标志字段不为空并且删除事务已提交,说明记录已被删除,不需要返回数据。

对于RC读已提交,同一个事务中的每次查询都会创建read视图,解决更新时读取产生的脏读的问题,不能解决读取时更新产生的不可重复读的问题。

对于RR可重复读,同一个事务中只有第一次查询会创建read视图,以后的查询都会使用已有的read视图,解决读取时更新产生的不可重复读的问题。

4.3.2 插入 *** 作

复制新纪录到undo日志,当事务提交时删除此条undo日志,当事务回滚时反向 *** 作然后删除和事务有关的undo日志。

插入记录,trx_id字段保存当前事务ID,roll_pointer字段为空。

4.3.3 修改 *** 作

复制原记录到undo日志。

修改记录,trx_id字段保存当前事务ID,roll_pointer字段保存undo日志。

4.3.4 删除 *** 作

复制原记录到undo日志。

修改记录,trx_id字段保存当前事务ID,roll_pointer字段保存undo日志,删除标志设为true。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5697537.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存