MySQL 是怎么加锁的? (重点阅读)
锁
64. 进阶-锁-介绍_哔哩哔哩_bilibili (黑马的MYSQL)
表锁和行锁的区别
表锁: 加锁快,不会出现死锁。不过,触发锁冲突的概率最高,高并发下效率极低。MyISAM 和 InnoDB 引擎都支持表级锁。
行锁: MySQL 中锁定粒度最小的一种锁,是 针对索引字段加的锁,只针对当前操作的行记录进行加锁。
加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。由存储引擎实现。
> 只有通过索引条件检索的数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁
理解针对索引字段加锁:行锁是加在索引上的,比如某个表中id字段是主键,如果给id=2这条记录加锁,那这把锁是加在主键索引(聚簇索引)。如果为某个没有索引的字段加锁,最终会造成全表扫描在主键索引上锁住所有相关的记录(例age=3,age不是索引)。
哪些操作会加表级锁?哪些操作会加行级锁?请简单举例说一下。
执行DDL语句alter table 修改表 或 truncate table清空表 时,会加表锁。
一般 update delete insert 对表的写操作语句都加的是行锁。只是如果没有命中索引情况下会全表扫描,导致给记录都加上行锁把全表锁住。(所以说加行锁的前提是 命中索引 ,否则会将所有记录加行锁)
mysql中还有什么锁?表锁行锁全局锁
还有全局锁。全局锁:通过flush tables with read lock语句会将整个数据库就处于只读状态了,这时其他线程执行以下操作,DDL或者DML / 增删改或者表结构修改都会阻塞。
全局锁主要应用于做全库逻辑备份,这样在备份数据库期间,不会因为数据或表结构的更新,而出现备份文件的数据与预期的不一样。
InnoDB 有哪几类行锁?MySQL 是怎么加锁的?
InnoDB 行锁是通过对索引数据页上的记录加锁实现的,MySQL InnoDB 支持三种行锁定方式:
- 记录锁(Record Lock) :也被称为记录锁,属于单个行记录上的锁。
- 间隙锁(Gap Lock) :锁定一个范围,不包括记录本身。 (只有RR隔离级别)
- 临键锁(Next-Key Lock) :其实就是记录锁+间隙锁,锁定一个范围,包含记录本身,主要目的是为了解决幻读问题(MySQL 事务部分提到过)。记录锁只能锁住已经存在的记录,为了避免插入新记录,需要依赖间隙锁。 (只有RR隔离级别)
MySQL 如何使用乐观锁和悲观锁?
我的描述:
因为MYSQL的并发控制是锁和MVCC共同实现的。
MVCC就是乐观锁的实现
行锁啊表锁就是悲观锁的实现
😖Next-Key Lock 的加锁范围?
73. 进阶-锁-行级锁-间隙锁&临键锁1_哔哩哔哩_bilibili
临键锁的加锁范围,一般是左闭右开,举个例子。
有如下数据
执行select * from tb where id >= 92 for update; 或者
select * from tb where id >= 92 lock in share mode;
这时会加一个记录锁,三个临键锁(临键锁=记录锁+间隙锁):
- 92的记录锁,锁住92
- 94的Next-Key Lock,范围(92,94]
- 98的Next-Key Lock,范围(94, 98]
- 正无穷的Next-Key Lock,范围(98,+∞)
什么时候加间隙锁?(了解了解)
73. 进阶-锁-行级锁-间隙锁&临键锁1_哔哩哔哩_bilibili
首先需要保证在RR隔离级别下
针对当前读 和 select delete update (黑马mysql课有说)
- 当前读 范围查找的时候,这个肯定加间隙锁(有索引的情况下)。
- 当前读 (及update、delete)查一个不存在的值的时候(有索引的情况下)。
- 在非唯一索引(普通索引上)进行等值查询( select,update、delete )时,也会加间隙锁(这个情况比较特殊,因为非唯一索引可能有多个相等的值,所以不能只加记录锁)。
总结:唯一索引范围查询 或 使用当前读(包括update和delete)一个不存在的值,非唯一索引进行等值查询。
拓展:插入语句会加间隙锁吗?(字节)
会加意向锁,间隙锁待考究
表锁中的: 了解意向锁吗?
69. 进阶-锁-表级锁-意向锁_哔哩哔哩_bilibili
- 为了避免行锁与表锁的冲突,innodb在加行锁的同时会给表加上意向锁,
- 后续如果有DDL语句给表加锁的时候会判断与意向锁(分共享和排他)是否冲突。
- 这样后续表锁就不需要检查每个行是否都加了行锁。
哪些语句会加意向锁?
加行锁的语句都会加意向锁。
意向共享锁(IS): 由语句 select..lock in share mode添加。
意向排他锁(IX): 由 insert、update、delete、select...for update 添加。
表锁中的:元数据锁
系统自己加,防止DML和DDL语句冲突。了解即可。
MVCC
86. 进阶-InnoDB引擎-MVCC-undolog版本链_哔哩哔哩_bilibili (优质课程)
10分钟带你深刻理解MySQL中的MVCC机制_哔哩哔哩_bilibili
MVCC是什么
是多版本并发控制。实现事务的并发控制。
核心思想就是读不加锁,读写不冲突。
MVCC的实现依赖于 ****数据快照(其实就是undo log, 也可以叫版本链)和 read view,不同的事务访问不同版本的数据快照,从而实现事务的隔离级别。
MVCC详细一点的实现。InnoDB存储引擎对MVCC的实现
首先要知道MVCC依赖于数据快照(其实就是undo log, 也可以叫版本链)和 read view实现 。
undo log记录了数据的历史版本,undo log版本链中主要隐藏字段有(回滚指针、修改当前记录的事务id、DB_ROW_ID)
readView (由事务生成)用于判断事务对于版本链中的哪些事务可见:
-
- 首先记住(先不用管为什么),在RC级别下,事务每次select前都会生成一个readview;在RR级别下,事务只有第一次select时会生成readView。
- readView中主要字段(记住m_ids中记录了什么就行):
m_creator_trx_id是创建readview的事务、m_ids是 创建该readview时系统中活跃的事务、m_low_limit_id:目前出现过的最大的事务 ID+1、m_up_limit_id:活跃事务列表 m_ids 中最小的事务 ID。 - 如何判断:注重理解(以RR隔离级别为例) :如上图,假如有个事务4 需要查这条记录,这时事务2、3均未提交,那我事务4查的时候创建的readView中
m_ids字段有 [2, 3] ,事务4查版本链的时候,发现第一条trx_id为3,3在readView的m_id中,那不可见,继续查下一条trx_id为2,也在m_id中所以不可见,下一条trx_id为1,比m_up_limit_id字段小,那可见,所以事务4查到的是最下面那条数据版本。
readView:
| m_creator_trx_id | m_ids | m_up_limit_id | m_low_limit_id |
|---|---|---|---|
| 4 | 2,3 | 2 | 3+1=4 |
-
- 其实很容易理解: (为了保证RR可重复读 )
m_ids是我当前事务第一次读数据的时候系统未提交的事务,那么我如果查到了这些事务(或者比这些事务大的 事务id)修改的版本对我肯定不可见。 我只能查比m_ids中id小的事务修改的数据,因为这些事务在我第一次查的时候肯定已经提交了啊。
- 其实很容易理解: (为了保证RR可重复读 )
ReadView的产生时机
在RC隔离级别下,事务每次select都会生成一个readview。
在RR隔离级别下,事务第一次select生成readView,所以可避免不可重复读的问题。
对ReadView产生时机的理解:
- 在RC隔离级别下,每次读都生成新的readView,那当然就会出现不可重复读的问题,因为每次读的readView都是新产生的。
- 在RR隔离级别,只有第一次读产生readView,后续读都用第一次读的readView,所以不会出现两次读不一致的情况。
MVCC用在哪个隔离级别?
RR和RC
当前读和快照读有什么区别?(前提,先理解MVCC)
快照读:不加锁,读的是版本链的历史数据,默认的select都是快照读,默认的select。
当前读:读取最新的数据,并且加锁 (如果数据已经有锁将阻塞),如update、delete、select for update、select in share mode都是当前读。
锁和MVCC
RC和RR级别的具体实现有什么不同呢?理解(面经)
从锁和MVCC两个方面分析
首先在innoDB中,RR隔离级别是很大程度上解决了幻读的
- MVCC:MVCC中,RR和RC隔离级别生成readView的时机不同
-
- RC每次都有readView所以会出现不可重复读(两次读的结果不一致)
- RR使用第一次的readView可避免可重复读和幻读。
- 锁 : RC只有行锁,RR有行锁间隙锁临建锁
-
- RC只有行锁,不能解决幻读问题(前后两次范围查询结果不一致)。
- RR有间隙锁和临键锁,锁行记录和间隙,避免其他事务在范围内插入。
MySQL可重复读隔离级别是否完全解决幻读了,分两种情况。
(很大程度上解决了)从锁和MVCC两个方面分析
- 针对快照读(普通 select 语句),是通过 MVCC 方式解决了幻读,因为可重复读隔离级别下,事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,即使中途有其他事务插入了一条数据, 是查询不出来这条数据的 ( 因为新加入的数据的trx_id在readView的m_ids中或者> m_low_limit_id ) ,所以就很好了避免幻读问题。
- 针对当前读(select ... for update 等语句),是通过 next-key lock(记录锁+间隙锁)方式解决了幻读,因为当执行 select ... for update 语句的时候,会加上 next-key lock,如果有其他事务在 next-key lock 锁范围内插入了一条记录,那么这个插入语句就会被阻塞,无法成功插入,所以就很好了避免幻读问题。
出现幻读的场景
- xxxx