开销 | 加锁速度 | 锁定粒度 | 发生锁冲突的概率 | 并发度 | 是否会出现死锁 | |
---|---|---|---|---|---|---|
表级锁 | 小 | 快 | 大 | 最高 | 最低 | 不会 |
行级锁 | 大 | 慢 | 小 | 最低 | 最高 | 会 |
页级锁介于两者之间,会出现死锁
全局锁
对整个数据库实例加锁。
整个库处于只读状态,不能对数据进行增删改,不能有DDL操作。
应用场景:逻辑备份。
注:
物理备份:以磁盘块为单位将数据从主机复制到备机。
逻辑备份:以文件为单位将数据从主机复制到备机。
表级锁
读锁时,其他进程无法对表进行写操作
写锁时,其他进程无法进行读操作
写操作加锁原理:
如果表上没有锁,就在表上加写锁,否则把写锁请求放在写锁队列中
读操作加锁原理:
如果表上没有写锁,就在表上加读锁,否则把锁请求放在读锁队列里
表锁使用一次封锁,在会话开始的地方使用 lock 命令将后面所有要用到的表加上锁,在锁释放之前,我们只能访问这些加锁的表,不能访问其他的表,最后通过 unlock tables 释放所有表
锁。
当表数据大部分时间只需要读取时,速度大于级锁和行级锁
有很多group by操作时没有写操作时,速度更快
行级锁
共享锁:
多个事务只能读,不能修改数据。
排他锁:
事务不能在排他锁上加其他锁,InnoDB的增删改都会给涉及的数据加排他锁,select语句不会加任何锁。
MySQL InnoDB引擎自动给增删改语句添加排他锁。
InnoDB行锁是通过给索引项加锁来实现的,只有通过索引条件检索数据时,InnoDB才使用行锁,否则就使用表锁。
缺点:
比表级、页级锁占用更多内存
当表数据被频繁使用时,比页级或表级锁定速度慢,因为需要获得更多锁
如果经常使用group by或者必须扫描整张表,比其他锁慢
记录锁、间隙锁、next-key锁
记录锁会锁住一行的记录
间隙锁或锁住指定区间的记录
next-key锁包含了记录锁和间隙锁,锁定范围也锁定记录本身。
InnoDB默认的加锁方式是next-key锁。
InnoDB自动使用间隙锁的条件:
- 必须在RR级别下
- 检索条件必须有索引(没有索引的话,mysql会全表扫描)
解锁规则
- 使用 UNLOCK TABLES 语句可以显示释放表锁
- 如果会话在持有表锁的情况下执行 LOCK TABLES 语句,将会释放该会话之前持有的锁
- 如果会话在持有表锁的情况下执行 START TRANSACTION 或 BEGIN 开启一个事务,将会释放该会话之前持有的锁
- 如果会话连接断开,将会释放该会话所有的锁
MyISAM的锁机制
MyISAM总是一次性获得所需的全部锁,要么全部满足,要么全部等待。所以不会产生死锁,但是由于每操作一条记录就要锁定整个表,导致性能较低,并发不高。
InnoDB的锁机制
意向锁
InnoDB可以同时实现表锁和行锁,使用索引处理数据时使用行锁,否则使用表锁。意向锁是为了保证行锁和表锁不冲突的存在。
例如,事务A修改某表的某行记录,会给该行上X锁,同时给整张表上IX锁;这样后面的事务就不能对该表加表级X锁。
意向锁都是表级锁,因为事务企图想加表级X锁时,检查该表有没有意向锁就行了,就不用检查这张表的每行数据是否都加了意向锁。
行锁的解锁
在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。
所以,如果事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。
InnoDB死锁
死锁发生的情形
- 一个线程两次申请锁
- 两个线程互相申请对方的锁,但是对方都不释放锁
死锁检测
针对死锁有两种策略:
一种是超时终止线程,这种时间不好把握,InnoDB默认是50秒
另外一种是自动检测事务死锁,并立刻回滚,然后返回错误。回滚通常选择undo量最小的事务
第二种比较灵活,但是相对会比较花费CPU资源。计算复杂度是n^2,也就是如果有1000个线程,死锁检测的时间复杂度是100万量级。
针对这个问题有一些解决方式:
首先判断这个场景下死锁检测是不是必须的,如果不是必须的可以关闭死锁检测;
或者可以控制并发的数量,例如设置某一行的update并行事务要在10个以下;
或者分摊一下字段压力,例如某个字段记录了公司的总营收,可以将这个字段分摊为10个字段,每次修改其中一个字段的数值即可。