mysql锁

89 阅读7分钟

表结构分类

行锁

默认情况下,是不需要锁的,默认是开启MVCC机制的,所以读取数据和修改数据完全不会互相影响,直接根据undolog版本链和ReadView来进行读取即可

共享锁

共享锁又被称为S锁

默认查询数据不会开启共享锁的,是走mvcc机制读快照版本,但是可以手动添加

#加共享锁
select * from vpm_project lock in share mode
#查询之后还是更新,这样就给查询语句加上了独占锁
select * from vpm_project for update

生产情况下,是不会开启共享锁的,因为根据这样会造成性能下降,而且根据mvcc机制读快也可以保证数据的准确性

如果非要在查询的时候加锁,通常都是在redis/zookeeper分布式锁来控制系统的锁逻辑,因为你如果直接在数据中加上复杂业务的锁逻辑,锁逻辑会隐藏在sql语句中,这对于java来说不太好维护

lock in share mode 只锁覆盖索引

独占锁

默认更新数据的时候是开启的

关系

读取数据其实就是 select

修改数据其实就是 update delete insert

间隙锁

gap lock 官方称为 LOCK_GAP

作用:只是为了给这条记录前面的间隙插入数据

这个锁的设计之初就是为了防止插入幻影记录

这个锁也有S锁和X锁两种,但是其实并没有什么卵用,并不妨碍其他事务的插入

infimum:表示一个表中的最小记录

supremum:表是一个表中的最大记录

比如你要防止在id为20之后中插入新的记录,你就可以给所在页面的supremum加上一个gap锁

Next-Key Lock

作用:既想要给当前记录加锁,又想要阻止其他事务在该记录前面的间隙插入新纪录

本质就是record lock和gap lock的个体

  • 这个锁只有在可重读的情况下才会生效,在可重复读的情况下,mysql加锁的基本单位就是next-key lock
  • next-key lock 是前开后闭区间

insert intention lock

插入意向锁 官方名称为 LOCK_INSERT_INTENTION

一个事务在插入一条记录的时候,需要判断插入位置是否已经被别的事务加入了gap锁,如果有的话,插入操作需要等待,知道等待拥有gap锁的按个事务提交为止。

innodb规定,这种也需要生成一个锁,于是命名为insert intention lock

这个锁比较“鸡肋”,不会阻拦任何其他事务的操作

record lock

我们自己给它起个别名叫 记录锁 官方 名称为 LOCK_REC_NOT_GAP

就是给数据库里的一条记录加锁,这中锁也有S锁和X锁两种

和基础的S锁和X锁一样

写锁和写锁 读锁都冲突

读锁和读锁不冲突,和写锁冲突

隐式锁

事务id其实就是一个隐式锁

其他所有的锁,加锁之后都会在内存中创建一个锁记录关联,只有隐式锁不会

两段锁协议

在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。

如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。

出现死锁之后的解决办法:

  • 一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置。
  • 另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。

如何解决热点行更新导致的性能问题?

  1. 如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关闭掉。一般不建议采用
  2. 控制并发度,对应相同行的更新,在进入引擎之前排队。这样在InnoDB内部就不会有大量的死锁检测工作了。
  3. 将热更新的行数据拆分成逻辑上的多行来减少锁冲突,但是业务复杂度可能会大大提高。

表锁

执行DDL语句的时候,默认会加上表锁,这是通过数据库的元数据锁实现的,也就是Metadata Locks

DDL语句和增删改操作,确实是互斥的

共享锁

#加表级共享锁
lock tables 表名 read;

独占锁

#加表级独占锁
lock tables 表名 write;

意向共享锁

当有事务在表里执行增删改操作的时候,会默认在行级加独占锁,同时也会在表级加一个意向独占锁

意向独占锁

当有事务在表里执行查询操作的时候,会默认在表级加一个意向共享锁

关系

全局锁

对整个数据库实例加锁。

MySQL提供加全局读锁的方法:Flush tables with read lock(FTWRL)

这个命令可以使整个库处于只读状态。使用该命令之后,数据更新语句、数据定义语句和更新类事务的提交语句等操作都会被阻塞。

使用场景:全库逻辑备份。

风险:

1.如果在主库备份,在备份期间不能更新,业务停摆

2.如果在从库备份,备份期间不能执行主库同步的binlog,导致主从延迟

官方自带的逻辑备份工具mysqldump,当mysqldump使用参数--single-transaction的时候,会启动一个事务,确保拿到一致性视图。而由于MVCC的支持,这个过程中数据是可以正常更新的。

一致性读是好,但是前提是引擎要支持这个隔离级别。

如果要全库只读,为什么不使用set global readonly=true的方式?

1.在有些系统中,readonly的值会被用来做其他逻辑,比如判断主备库。所以修改global变量的方式影响太大。

2.在异常处理机制上有差异。如果执行FTWRL命令之后由于客户端发生异常断开,那么MySQL会自动释放这个全局锁,整个库回到可以正常更新的状态。而将整个库设置为readonly之后,如果客户端发生异常,则数据库就会一直保持readonly状态,这样会导致整个库长时间处于不可写状态,风险较高。

加锁条件

  • 在同一个事务中进行加锁操作
  • 被加锁的记录在同一个页面中
  • 加锁的类型是一样的】
  • 等待状态是一样的

同时满足上面四个条件,就可以将多个所记录的锁放在同一个锁结构中

加锁规则总结

可重复读

  • 加锁的基本单位是 next-key lock。希望你还记得,next-key lock 是前开后闭区间。
  • 查找过程中访问到的对象才会加锁。
  • 等值查询,给唯一索引加锁的时候,next-key lock 退化为行锁。如果不是唯一索引,向右遍历时且最后一个值不满足等值条件的时候,next-key lock 退化为间隙锁。
  • 范围查询,无论是否是唯一索引,范围查询都需要访问到不满足条件的第一个值为止
  • for update 默认的是更新之后还要进行查找,所以不仅给索引上锁,符合条件的主键索引页会上锁
  • 唯一索引会找到对应范围就会停止,但是非唯一索引是需要多找一段
  • 范围索引InnoDB 会往前扫描到第一个不满足条件的行为止
  • 删除的时候limit,不仅可以控制删除数据的条数,让操作更安全,还可以减小加锁的范围

读提交

语句执行过程中加上的行锁,在语句执行完成后,就要把“不满足条件的行”上的行锁直接释放了,不需要等到事务提交。