既然要全库只读,为什么不使用 set global readonly=true 的方式呢?
表锁大致可以分成两种:表锁,元数据锁(MDL锁)
如何安全地给小表加字段?首先,干掉长事务,避免不必要的锁等待。其次,设置等待时间,反复重试。undefined查询MDL锁可以使用:
show processlist // 需要设置performance_schema on 大约会有10%性能损失
(各个引擎对于行锁的实现方式不一样)
与表锁的一些比较:
select * from t sys.innodb_lock_waits where locked_table = 'db'.'table'
在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。
因此,对于一个事务中语句执行顺序有一个大致的原则:如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放
UPDATE users SET name = "saurfang" WHERE id = 3;
最基本的锁,锁住ID为1的这一行数据。锁会加在索引上,如果没有主键索引,那么会加在row_id上。如果查询的是二级索引,会回到主键索引上,并加锁。
当查询没有索引时,会走全表,把查到的每一行都加锁,在RC(读提交)下,加锁的语句执行完成后,就会直接释放掉不符合要求的行锁。因此,如果一条更新语句没有走索引,会花费极大的开销。
我们之前提到过一个幻读的问题,在RR(可重复读)的隔离级别下,解决方法就是间隙锁。
间隙锁,锁住的是两个行之间的数据,不允许其他人向中间写入一个数据。比如在2-4 之间加上间隙锁,那么其他人在写入 3的时候就不会成功。
以上面的sql 为例子,MySQL会给id=3 这行的前后索引之间的间隙都加上锁。当多个事务同时持有这一行间隙锁的时候是不会出现冲突的,因为跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。 (这里的写入指的是insert,更新操作是不会被锁住的)。
实际上,目前遇到的多数业务中,对于数据一致性的要求不是非常非常的高,出现幻读也不是非常严重的问题,可以把隔离级别降到RC(读提交)这样可以提高并发性。间隙锁虽然彼此不冲突,本身也是花费一些开销,而且会和写入操作发生冲突,影响并发。另外,所谓“间隙”,其实根本就是由“这个间隙右边的那个记录”定义的。
可以认为是记录锁和间隙锁的组合。是一个前开后闭区间。比如上边的sql,Next-Key Locks加的锁就是(2,3]。意思就是,加了(2,3)的间隙锁,又加了3 的行锁
无论主键索引还是二级索引,都会加上间隙锁。Next-Key Locks 因为包含行锁,会出现冲突。
只有在insert的时候会使用,和间隙锁冲突,但是彼此不冲突。比如两个写入的事务都有(1,5)的意向锁,一个写入2,一个写入4,不会发生冲突。如果(1,5)之间有间隙锁,那么他们都会个间隙锁发生冲突。
表锁和行锁是互相冲突的。如果一个行锁只锁住了一行数据,这时要申请一下表锁,那么会遍历表,看看是否存在行锁,开销很大。为了解决这个问题,会先在表上加上意向锁,然后再执行行锁操作。这样就可以避免上述问题。
意向锁之间是不会产生冲突的,也不和 AUTO_INC 表锁冲突,它只会阻塞表级读锁或表级写锁,另外,意向锁也不会和行锁冲突,行锁只会和行锁冲突。
根据极客时间的《Mysql实战45讲》中的说明,对于加锁的基本规则大致为5个,包含了两个“原则”、两个“优化”和一个“bug”:
CREATE TABLE `user` (
`id` int NOT NULL,
`name` int DEFAULT NULL,
`age` int DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `age` (`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin;
BEGIN;
INSERT INTO `user` VALUES (1, 0, 0);
INSERT INTO `user` VALUES (2, 1, 5);
INSERT INTO `user` VALUES (3, 2, 10);
INSERT INTO `user` VALUES (5, 3, 15);
INSERT INTO `user` VALUES (10, 4, 20);
INSERT INTO `user` VALUES (15, 5, 25);
COMMIT;
假设我们数据库里的数据是这样的。我们对sql依次分析:
第一类:主键等值查询与普通索引等值查询
update user set age = 15 where id = 5;
首先加上(3,5]的next-key lock(原则1);然后找到了id=5 的这一行,next-key lock退化为行锁(优化1)。此时其他事务可以写入一个ID=4的数据。
update user set age = 15 where id = 4; #主键索引
首先,ID=4 不存在,向后查到ID=5加上(3,5]的next-key lock(原则1);然后next-key lock退化为间隙锁(优化2)锁住了(3,5)。此时其他事务无法写入一个ID=4的数据。
select ID from user where age = 10 lock in share mode #普通索引 覆盖索引
这个语句走了普通索引,只查询id,所以会走覆盖索引,不用回表。此时会查到age (5,10],然后退化为age=10 的行锁(优化1),继续往后查到(10,15],然后退化为间隙锁(10,15)(优化2)。
注意! 根据原则2,此时主键索引不会加锁。因此,通过主键更新name 不会被锁住,原因是当前索引上没有name这个字段,但写入一行数据会被锁住。
lock in share mode 只锁覆盖索引。for update ,系统会认为你接下来要更新数据,因此会顺便给主键索引上满足条件的行加上行锁。
第二类:范围查询
select * from user where id>=10 and id<11 for update; #主键范围查询
首先,给ID=10 加行锁(同上边);然后,继续向后查到ID=15,加(10,15]的next-key lock。此时实际加锁范围是10,15
select * from user where age >=10 and age<11 for update; #普通索引范围查询
首先,给普通索引(5,10]加锁,此时age 索引不是唯一索引,不能走优化1;然后,继续向后查到ID=15,加(10,15]的next-key lock。此时实际加锁范围是(5,15]
select * from user where id >5 and id <= 10 for update; #主键范围查询
按理说,给(5,10]加上锁就行了,实际上,会继续向后查,给(10,15]加上锁。这就是上面说的BUG
我们前面说加锁指的是 next-key lock。实际的加锁顺序分成两步,第一步加间隙锁,第二步加行锁。我们之前说,间隙锁彼此不冲突,一个间隙可以很多个事务持有间隙锁,但是行锁只有一个事务持有,其他就处在等待状态了。
在执行过程中,通过树搜索的方式定位记录的时候,用的是“等值查询”的方法。
当查询很多行数据时,锁是一个一个加上去的,并不是一起加的。 在实际工作中,可以遵循以下规则:
当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁。
举一个简单的死锁例子:
#TRANSACTION 1
begin;
update user set age = age + 1 where id = 1;
update user set age = age + 1 where id = 2;
#TRANSACTION 2
begin;
update user set age = age + 1 where id = 2;
update user set age = age + 1 where id = 1;
此时,事务1 在等事务2 放开 ID= 2 的行锁,事务2在等事务1 放开ID=1 的行锁,出现了死锁。
处理死锁的两个策略:
在 InnoDB 中,innodb_lock_wait_timeout 的默认值是 50s,意味着出现死锁后,第一个被锁住的线程要过 50s 才会超时退出,其他线程才有可能继续执行。这个开销有点大。死锁检测,就是出现事务被锁,就检查下他所依赖的线程有没有被其他锁住。
死锁检测有一定的性能损耗,如果并发很大的话,会导致CPU负载很高,但是并发量却上不去。
show engine innodb status 可以用来排查死锁信息
Insert 语句如果出现唯一键冲突,会在冲突的唯一值上加共享的 next-key lock(S 锁)。因此,碰到由于唯一键约束导致报错后,要尽快提交或回滚事务,避免加锁时间过长
乐观锁与悲观锁,可以认为是一种基于业务需要的特殊的锁。其中,乐观锁需要依赖业务逻辑来实现,悲观锁则直接使用select……for update 来实现。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有