行锁

MySQL 的行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁。比如 MyISAM 引擎就不支持行锁。InnoDB 是支持行锁的,这也是 MyISAM 被 InnoDB 替代的重要原因之一。

两阶段锁

在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁的申请时机尽量往后放。

死锁和死锁检测

当并发系统中不同线程出现循环资源依赖,涉及的线程都在等待别的线程释放资源时,就会导致这几个线程都进入无限等待的状态,称为死锁。

事务A
事务B
begin;update test set k=k+1 where id=1;begin;
update test set k=k+1 where id=2;
update test set k=k+1 where id=2;
update test set k=k+1 where id=1;

这个时候 A 在等待事务 B 释放 id=2 的行锁,而事务 B 在等待事务 A 释放 id=1 的行锁。事务 A 和事务 B 在互相等待对方释放资源形成死锁。当出现死锁后有两种策略:

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

在 InnoDB 中,innodb_lock_wait_timeout 的默认值是 50s,这就表明如果出现死锁需要等待 50s 第一个被锁住的线程才会退出,这对业务是无法接受。如果我们把这个值设为很小的值,比如 1s ,如果只是简单的锁等待也会被“误伤”。

正常情况下我们采用第二种策略,即:主动死锁检测,而且 innodb_deadlock_detect 的默认值本身就是 on。主动死锁检测在发生死锁的时候,是能够快速发现并进行处理的,但是它也是有额外负担的。每当一个事务被锁的时候,就要看看它所依赖的线程有没有被别人锁住,如此循环,最后判断是否出现了循环等待,也就是死锁。

如果所有事务都要更新同一行,每个新来的被堵住的线程,都要判断会不会由于自己的加入导致了死锁,这是一个时间复杂度是 O(n) 的操作。假设有 1000 个并发线程要同时更新同一行,那么死锁检测操作就是 100 万这个量级的。虽然最终检测的结果是没有死锁,但是这期间要消耗大量的 CPU 资源。

解决由这种热点行更新导致的性能问题主要方法:

  • 一种头痛医头的方法,就是如果你能确保这个业务一定不会出现死锁,可以临时把死锁检测关掉。关掉死锁检测意味着可能会出现大量的超时,这是对业务有损的。
  • 是控制并发度。如果并发能够控制住,比如同一行同时最多只有 10 个线程在更新,那么死锁检测的成本很低。这个并发控制要做在数据库服务端。如果你有中间件,可以考虑在中间件实现;如果你的团队有能修改 MySQL 源码的人,也可以做在 MySQL 里面。基本思路就是,对于相同行的更新,在进入引擎之前排队。如果做在客户端,当客户端数量过多时,也会造成性能问题。
  • 通过将一行改成逻辑上的多行来减少锁冲突。比如更新一条账户的金额,可以把金额拆成多条,每次更新随机选择一条。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值