Mysql Server 层支持
lock tables t1 read 加读锁,会限制本线程只能读 t1 ,其他线程写 t1 会被阻塞
lock tables t2 write 加写锁,会限制本线程只能读写 t2 ,其他线程不能读也不能写 t2,都会被阻塞
InnoDB 引擎支持行锁,共享锁(S)和 独占锁(X),执行更新操作时,会自动加 X 锁,对于普通的查询语句,InnoDB 不会主动加任何锁,可以显示的加锁。
select * from t where ... lock in share mode // 加共享锁 S
select * from t where ... for update // 加独占锁 X
更新锁
只有行级更新锁。
意向锁
InnoDB 的行锁是加载索引上的,如果我们的更新没有走索引导致扫描全表,就会锁全表,锁的是主键索引。
InnoDB 还支持一种特殊的表锁 意向锁,我们先来看一个例子:
事务A:update t set k = 1 where id = 6
事务B:update t set k = 1
事务A:commit
事务A 会拿 id = 6 这一行的 独占锁,事务B 对整个表进行修改,要对整个表所有行加独占锁,那么需要遍历主键索引树的每个节点,看有没有被别的事务加了共享锁或者独占锁,最终 发现 id = 6 被加了 独占锁,事务B加锁失败,阻塞。这种场景性能很差,如果要对全表加锁,需要遍历索引树检测锁状态。InnoDB 使用意向锁来解决这个问题
意向锁是一种不与行锁冲突的表级锁,就是为了解决锁全表场景的锁状态判断的性能问题。意向锁也是InnoDB自动添加的。
常见的意向锁
1.意向共享锁(Intent Share Lock) IS 锁
事务 T1 在给数据行对象添加行级 S 锁前,要先获得 IS 锁。(事务在加共享锁之前 必须拿到表的 意向共享锁)。如果表被加了 IS 锁,说明某个事务对这个表中的某些数据行加了行级 S 锁。当其它事务想要在这个表上加一个表级排他锁时,发现这个表已经加了意向共享锁,那么就不可以加表级的排他锁了。
2.意向排他锁(Intent Exclusive Lock)IX 锁
事务在请求行级 X 锁前,要先获得 IX 锁(事务在加独占锁之前 必须拿到表的 独占共享锁)。
事务 T1 修改 user 表的数据行对象 A,会给数据行对象 A 上一把行级的排他锁,但是在给数据行对象 A 上行级排他锁前会先给 user 表上一把意向排他锁,这时事务 T2 要给 user 表上一个表级的排他锁就会被阻塞。
3.共享意向排他锁(Share Intent Exclusive Lock) SIX 锁
共享意向排他锁的意思是,某事务要读取整个表,并更新其中的某些数据。
意向锁是表级锁和行锁是不互斥的,只有在需要锁全表的行锁的情况下,才会出现互斥的情况,所以意向锁不是传统意义上的表锁,性能比较高。
考虑到意向锁,事务A 会拿 id = 6 的行锁 和 表的意向独占锁,事务A 需要锁全表行锁,直接加锁失败。
where条件是一个范围时,数据库会锁定区间数据,主要是解决幻读问题。
使用场景:(1)防止幻读;(2)范围查询;
缺点:(1)性能影响;(2)死锁;(3)复杂性;(4)锁定范围过大,可能导致不必要的锁定冲突;
对索引记录之间的间隙进行加锁,当使用范围查询记录或者更新数据,InnoDB 会给满足条件的的索引记录加锁,而满足查询条件但是不存在的记录集合,称为 间隙 (GAP),InnoDB 也会对这个"间隙"加锁,也叫 GAP Lock,这是为了防止拿锁期间其他的事务插入数据,导致出现幻读现象,读提交级别因为不考虑幻读问题,所以不需要加间隙锁。
间隙锁和被锁的间隙之间是互斥的,间隙锁之间是不存在互斥的。
这里要注意间隙锁 锁的是索引记录项之间的间隙,而并不是索引值之间的间隙,后面 next key-lock 会有演示
可以理解为一种特殊的间隙锁,临键锁可以解决幻读的问题。
当事务拥有某一行记录的临键锁时,会锁住一段左开右闭的区间。比如后面截图中的3条数据,就生成了4个临键锁,临键锁如下:(1)(-∞,20];(2)(20, 25];(3)(25, 30];(4)(30, +∞];
当更新age=25的记录时,不能增加或者修改age为(25, 30]之间的数
是 InnoDB 加行锁和间隙锁的算法,是一个前开右闭的区间
update t set k = 1 where id = 5
这里会对 id = 5 记录加 Next key-Lock(0,5] 实际就是(0,5)之间的间隙锁和 id = 5 的行锁 组合,当然因为是id是唯一索引,Next key-Lock 会退化成 id = 5 的行锁。
Next key-Lock 加锁规则如下:
(1)、只有访问到的索引记录才会加锁,这里要理解访问到的记录并不一定是查询到的结果
select * from t where id >= 5 and id < 10 for update
Mysql 会通过主键索引查询到 id = 5 的索引记录,注意这里是等值查询,继续往右扫描满足条件的索引记录 ,从这开始就是范围查询了,找到下个记录 id = 10 ,不满足条件退出,那么这里就访问到了 id = 5 ,id = 10 两条记录,都会被加上 Next key-Lock。
(2)、索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。
SQL1: update t set k = 1 where id = 5
SQL2: select * from t where id >= 5 and id < 10 for update
SQL1 会加 (0,5] Next key-Lock,因为 id 是主键索引,会退化成 id = 5 的行锁,SQL2 会对 加 (0,5](5,10] 两个Next key-Lock,id = 5 是等值匹配 最终退化成 id = 5 的行锁 和 5,10]
(3)、非唯一索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
select * from t where k >=5 and k <= 10 for update
按照加锁规则,会加上 (0,5] ,(5,10] ,(10,15],因为 k = 10 等值查询,最后一条记录是 k = 15,所以退化成 (10,15),最终加的Next key-Lock 是 (0,5] ,(5,10] , (10,15)
4、间隙锁锁的是索引记录项之间的间隙,而并不是索引值之间的间隙
事务A:update set k = 1 where k = 5
事务B:update set k = 0 where k = 10 blocked
分析下这两个事务,如果按照索引的值那我们很容易得到 事务A 的间隙锁为 (0 ,5 )(5 ,10),两边都是开区间,那么事务B 应该不会被阻塞,而实际上事务B会被阻塞,这是因为 间隙锁是加载索引记录项之间的。
我们看索引 k 的结构,事务A 加的间隙锁应该是 ((0,0)(5,5)),((5,5)(15,5)),((15,5)(10,10)) 三个间隙锁,事务B 更新操作相当于插入一条(10,0) 的记录,此时需要在间隙锁中插入记录 ((0,0) (10,0)(5,5)),加锁失败阻塞
**5、**前面说了 Next key-Lock 实际上是InnoDB加锁的算法,是间隙锁和行锁的组合,并不是一个整体,InnoDB申请一个 key 的 Next key-Lock 按照访问记录的顺序去加行锁或者间隙锁。
事务A:select id from t where k = 10 lock in share mode;
事务B:update t set k = 1 where k = 10 ;(blocked)
事务A:insert into t values(8,8)
事务B:DeadLock ERROR
事务A 加 next-key lock ( 5,10 ] 和间隙锁 (10,15 ),具体的顺序是 先加 (5,10 ) 间隙锁、id = 10 的行锁 、 (10,15 )间隙锁 全部加锁成功。
事务B 同样需要加 加 next-key lock ( 5,10 ] 和间隙锁 (10,15 ),具体的顺序是 先加 (5,10 ) 间隙锁 成功,id = 10 的行锁 ,注意此时加锁失败阻塞。
事务A 插入( 8,8) 被事务B (5,10 ) 间隙锁阻塞。此时发生死锁,事务B被回滚。
mysql的innodb引擎的一种锁定机制,用于锁定和控制单个行记录的访问。
记录锁作用在索引上,对于没有主键和唯一键的表,innodb会自动添加隐藏的聚簇索引,并在该索引上加锁。
show processlist
show processlist:主要是查询数据库中哪些线程正在执行,针对比较慢的线程(time的数值比较大的线程)我们可以将其kill掉。此外,show full processlist 返回的结果是实时变化的。
解读:
Id:链接mysql 服务器线程的唯一标识,可以通过kill来终止此线程的链接。
User:当前线程链接数据库的用户
Host:显示这个语句是从哪个ip 的哪个端口上发出的。可用来追踪出问题语句的用户
db: 线程链接的数据库,如果没有则为null
Command: 显示当前连接的执行的命令,一般就是休眠或空闲(sleep),查询(query),连接(connect)
Time: 线程处在当前状态的时间,单位是秒
State:显示使用当前连接的sql语句的状态,很重要的列,后续会有所有的状态的描述,请注意,state只是语句执行中的某一个状态,一个 sql语句,已查询为例,可能需要经过copying to tmp table,Sorting result,Sending data等状态才可以完成
Info: 线程执行的sql语句,如果没有语句执行则为null。这个语句可以使客户端发来的执行语句也可以是内部执行的语句
结果处理:
针对执行时间比较长的线程,我们可以直接将其kill掉,直接执行 kill Id号即可。