锁机制详解
锁是计算机协调多个进程或线程并发访问某一资源的机制。
锁的分类
从性能
- 读操作较多的场景使用乐观锁(使用版本比对机制)
- 写操作较多的场景使用悲观锁
从数据操作粒度
- 表锁 (开销小,加锁快)
- 页锁 (只有BDB存储引擎支持页锁)
- 行锁 (开销大,加锁慢)
- 间隙锁 (是在可重复读隔离级别下才会生效)
- 临键锁 (Next-key Locks 是行锁与间隙锁的组合)
从数据操作
- 读锁 多个读操作可以同时进行,阻塞写
- 写锁 阻塞写锁和读锁
为了提升数据库加锁效率,是mysql数据库自己加意向锁(Intention Lock)
- 意向共享锁,IS锁,对整个表加共享锁之前,需要先获取到意向共享锁。
- 意向排他锁,IX锁,对整个表加排他锁之前,需要先获取到意向排他锁。
如何加锁
#加读锁
select * from T where id=1 lock in share mode
#加写锁
select * from T where id=1 for update
#事务中,同样一条sql语句,表现不一样
select * from account where id = 7 for update;
#如果存在id=7的记录,表现为行锁,无法对这条记录进行修改删除操作
#如果存在id>7的记录,表现为[7,next id)临键锁,无法在这个区间进行新增记录的操作
#如果不存在id>7的记录,表现为[7,max id)临键锁,无法在这个区间进行新增记录的操作
#如果id没有索引,在RR隔离级别会产生表锁,无法对表中任何记录进行操作
select * from account where id > 7 for update;
#表现为(7,max id)间隙锁
‐‐手动增加表锁
lock table account read;
‐‐删除表锁
unlock tables;
加锁时机
MyISAM在执行查询语句SELECT前,会自动给涉及的所有表加读锁,在执行update、insert、delete操作会 自动给涉及的表加写锁。 InnoDB在执行查询语句SELECT时(非串行隔离级别),不会加锁。但是update、insert、delete操作会加行锁。
死锁问题
大多数情况mysql可以自动检测死锁并回滚产生死锁的那个事务,但是有些情况mysql没法自动检测死锁, 这种情况我们可以通过日志分析找到对应事务线程id,可以通过kill杀掉。
kill trx_mysql_thread_id
锁优化实践
- 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
- 合理设计索引,尽量缩小锁的范围
- 尽可能减少检索条件范围,避免间隙锁
- 尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行
- 尽可能用低的事务隔离级别
MVCC机制
Mysql在读已提交和可重复读隔离级别下都实现了MVCC机制。
MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链对比规则读取同一条 数据在版本链上的不同版本数据
从字面上理解,RR就应该每次读取都一致。RC就是提交的事务都被读取。
实现原理
undo log版本链 + read view
当事务开启,执行任何查询sql时会生成当前事务的一致性视图read-view
read-view = 所有未提交事务id数组 + 已创建的最大事务id
查询结果从【undo log版本链】最新数据开始使用对比规则,获取快照结果。
版本链比对规则
trx_id 当前比对的事务id
min_id 所有未提交事务id数组最小事务id
max_id 已创建的最大事务id
- trx_id < min_id ,数据可见,结束比对。
- trx_id > max_id ,数据不可见,继续比对。
- min_id <=trx_id<= max_id
- trx_id 在 【所有未提交事务id数组】中,数据不可见,继续比对。
- trx_id 不在 【所有未提交事务id数组】中,数据可见,结束比对。
RC隔离级别与RR隔离级别使用相同的比对规则,不同的是,RC隔离级别每次都会生成新的read view。
如果事务本身执行过更新语句,则不使用上面的对比规则,直接使用当前最新的值。