MySQL的锁
锁粒度
全局锁
全局锁的典型使用场景是,做全库逻辑备份。
|
|
加全局锁备份的问题
- 如果你在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆;
- 如果你在从库上备份,那么备份期间从库不能执行主库同步过来的 binlog,会导致主从延迟。
使用支持事务的存储引擎备份
single-transaction
方法只适用于所有的表使用事务引擎的库。
为什么不用 set global readonly=true 的方式?
readonly
选项会用来执行其他逻辑处理,比如用它判断某个库是主库还是备库。因此,修改global
变量的影响范围更大,不建议使用这种方式。- 在异常处理机制上存在差异。如果发出
FTWRL
命令后,由于客户端发生了异常而断开连接,MySQL 就会自动释放全局锁,然后数据库就会回到可以继续更新的状态。但如果将整个数据库设置为readonly
,并在操作过程中发生了异常,数据库就会一直处于readonly
模式,并且会面临较高的风险。
表锁
表锁
与 FTWRL 类似,可以用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。
|
|
元数据锁(meta data lock,MDL)
MySQL 5.5 引入了MDL(Meta Data Lock)锁,它分为读锁和写锁。
- 读锁之间不会相互影响,所以你可以让多个线程同时进行增删改查操作。
- 但读写锁之间、写锁之间是互斥的,用来保证给表结构变更操作的安全性。因此,你要是有两个线程去给一张表加字段,其中一个需要等另一个先执行完毕,才能开始。
|
|
当你发现 lock tables
这样的语句时,这很有可能是因为你的数据库引擎不支持行锁导致的。两种情况:
- 你的系统正在使用
MyISAM
等不支持事务的引擎,那么就需要考虑升级数据库引擎; - 你数据库引擎已经升级,但是代码却没有跟着升级,此时你只需要把
lock tables
和unlock tables
改为begin
和commit
就可以解决问题了。
行锁
|
|
锁多个行,影响并发度的锁尽量往后放
在InnoDB事务中,行锁是需要时才加上的,但不会立刻释放,只有等到事务完成时,这个”两阶段锁协议“才会释放。
如果你的事务中需要锁定多行,最好是尽可能先锁定那些不太可能引起锁冲突行。例如,此应用程序要涉及以下操作:
- 从
顾客A的余额
中扣除电影票价; - 给
影院B账户余额
增加这张电影票价; - 记录一条交易日志。
从上面可以看出,多个用户对应一个影院账户,所以影院账户余额
的行引起锁冲突的概率最高,所以调整顺序为(3、1、2)。
InnoDB 的锁
共享(Shared)和排他(Exclusive )锁
- shared (
S
) locks 共享锁:T1持有行r的共享(S)锁,即T2请求对行r的锁定时,将以如下方式处理:- T2请求
S锁
可以立即授予。因此,T1和T2都持有行r的S锁。 - T2请求
X锁
不能立即授予。
- T2请求
- exclusive (
X
) locks 排他锁:如果事务T1在行r上拥有排他(X)锁,则来自不同事务T2的锁定请求不能立即满足。相反,事务T2必须等待事务T1释放其对行r的锁定。
意向(Intention)锁
InnoDB 支持多粒度锁定,可以允许行锁和表锁共存。例如,LOCK TABLES ……WRITE 将在指定的表上排他性锁定(X 锁定) 。为了使多级粒度的锁定实用,InnoDB 使用意向锁。意向锁是表级锁,它表明事务在表中的行上需要什么类型的锁定(共享或排他)。有两种类型的意向锁:
- intention shared lock (
IS
) 意向共享锁:事务打算在表中的各个行上设置共享锁。 - intention exclusive lock (
IX
) 意向排他锁:事务打算在表中的各个行上设置排他锁。
X | IX | S | IS | |
---|---|---|---|---|
X | 冲突 | 冲突 | 冲突 | 冲突 |
IX | 冲突 | 兼容 | 冲突 | 兼容 |
S | 冲突 | 冲突 | 兼容 | 兼容 |
IS | 冲突 | 兼容 | 兼容 | 兼容 |
记录(Record )锁
记录锁是索引记录上的锁。例如, SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;
阻止任何其他事务插入、更新或删除 t.c1
值为 10
的行。
间隙(Gap)锁
- 如果您将事务隔离级别更改为
READ COMMITTED
,间隙锁不生效。
Next-Key Locks
next-key lock
允许一个共享或可重复访问锁锁定 next-key
范围。也就是说,它同时锁住一行数据
,也锁定它之后的空间
,使其他事务不能插入这个范围内的任何数据。所以它是由 记录锁
和 间隙锁
组成的。
加锁规则
- 原则 1:加锁的基本单位是
next-key lock
。next-key lock
是前开后闭区间。 - 原则 2:查找过程中访问到的对象才会加锁。
- 优化 1:索引上的等值查询,给唯一索引加锁的时候,next-key lock 退化为
行锁
。 - 优化 2:索引上的等值查询,向右遍历时且最后一个值也不是需要找的值时,next-key lock 退化为
间隙锁
。 - 一个 bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。
死锁和死锁检测
当出现死锁的情况下,有两种可选的处理方式:
- 第一种办法是,直接进入等待状态,直到超时。你可以通过
innodb_lock_wait_timeout
这个参数去设定等待时间。 - 第二种办法是,启动死锁检测(Deadlock Detection),当死锁出现之后,便会主动回滚死锁链条中的某一个事务,以此让其他事务继续执行。要启动这个检测,需将
innodb_deadlock_detect
参数设置成on
。
查询长时间不返回
等 MDL 锁
show processlist
命令查看 Waiting for table metadata lock 。
sys.schema_table_lock_waits 显示哪些会话已被锁定,等待元数据锁定
|
|
等 flush
|
|
等行锁
sys.innodb_lock_waits 总结了事务正在等待的InnoDB锁。默认情况个,行按降序锁定年龄排序。
|
|
并发控制模式
乐观锁 Optimistic
乐观锁又称为无锁或版本锁,它使用版本戳来实现,某一记录在更新时,其版本必须是当前数据库中最新的版本。乐观锁不会对其他用户发起的读写操作施加任何约束,所以最大程度上减少了锁带来的性能开销。
优点:主要优点是没有使用锁,简化了事务处理,增加了访问数据库的速度,减少了死锁产生。
缺点:它更容易发生并发冲突,存在脏读取的情况,比如并发时两个事务由于版本不同而都可以提交。
|
|
- 读取“version_number”字段值;
- 执行更新操作,例如:
UPDATE my_table SET value = ? WHERE id = ? AND version_number = ?
; - 如果上一步更新成功,则执行
UPDATE my_table SET version_number = ? WHERE id = ? AND version_number = ?
; - 将事务提交。
悲观锁 Pessimistic
悲观锁则相反,即可能对其他用户的读写操作施加约束,所以性能开销也更大。悲观锁可以使用行级锁和表级锁实现,在多用户访问数据库的情况下,可以防止脏读取和不可重复读取。
优点:支持对数据进行细粒度、持久性的锁定,安全性高。
缺点:它降低了应用程序的整体性能,特别是在访问人数众多的情况下。
|
|