Mysql学习笔记--行锁

234 阅读5分钟

思考

1.Mysql的行锁由各个引擎自己实现的,但是并不是所有的引擎都是支持行锁的(MyISAM不支持),对于不支持行锁的引擎,并发控制只能通过表锁。

2.行锁:事务A更新一行,事务B也要更新这一行,此时就需要行锁。

3.行锁就是一锁锁一行或者多行记录,mysql的行锁是基于索引加载的,所以行锁是要加在索引响应的行上,即命中索引

数据库表中有一个主键索引和一个普通索引,Sql语句基于索引查询,命中两条记录。此时行锁一锁就锁定两条记录,当其他事务访问数据库同一张表时,被锁定的记录不能被访问,其他的记录都可以访问到。

4.行锁的特征:锁冲突概率低,并发性高,但是会有死锁的情况出现。

死锁和死锁检测

1.死锁其实就是,你等我释放锁,我等你释放锁,循环这么等,最后谁也等不到谁

2.解决办法:

(1)一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置。

默认情况下innodb_lock_wait_timeout为50s,也就是出现死锁后,第一个锁住的线程要过50s才能超时退出,然后其他线程继续执行,虽然50s对于产品体验不是很好,但是这个时间也不能设置特别短,会造成误判这种情况。

(2)另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。

不过死锁检测存在弊端:

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

如何应对这种没有执行几个事务,就占满CPU利用率的状况呢?

1.解决BUG的最快方法就是注释掉它,首先保证不会出现死锁的情况下,关掉死锁检测,当然这种是一种有风险的做法,当关闭死锁检测,出现死锁就会出现大量超时回滚的现象,三思而后行。

2.另一种控制并发度,就是更新的时候限制指定数量的线程同时更新,这种操作可以在中间件中完成。

实践

两阶段锁

事务1

begin;
update t set a=a+1 where id=1;
update t set a=a+1 where id=2;

[SQL] update t set a=a+1 where id=1; 受影响的行: 1 时间: 0.009s

[SQL] update t set a=a+1 where id=2; 受影响的行: 1 时间: 0.001s

事务2,事务的查询语句出现的阻塞情况

begin;
update t set a = a+1 where id = 1;

[SQL] update t set a = a+1 where id = 1;

此时将事务1的事务进行提交

begin;
update t set a=a+1 where id=1;
update t set a=a+1 where id=2;
commit;

[SQL] update t set a=a+1 where id=1; 受影响的行: 1 时间: 3.001s

[SQL] update t set a=a+1 where id=2; 受影响的行: 1 时间: 0.001s

此时事务2也执行成功

begin;
update t set a = a+1 where id = 1;

**[SQL] update t set a = a+1 where id = 1; 受影响的行: 1 时间: 8.316s
**

综上实验

在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。

死锁

事务1

begin;
update t set a=a+1 where id=1;

[SQL] update t set a=a+1 where id=1; 受影响的行: 1 时间: 0.001s

事务2

begin

[SQL]begin; 受影响的行: 0 时间: 0.001s

事务2

update t set a = a+1 where id = 2;

[SQL]update t set a = a+1 where id = 2; 受影响的行: 1 时间: 0.003s

事务1(阻塞)

update t set a=a+1 where id=2;

结果:阻塞

事务2(出现死锁警告)

update t set a = a+1 where id = 1;

[SQL]update t set a = a+1 where id = 1; 

[Err] 1213 - Deadlock found when trying to get lock; try restarting transaction

当死锁超时的时候系统会自动回滚然后释放锁

[SQL]update t set a = a+1 where id = 1; [Err] 1205 - Lock wait timeout exceeded; try restarting transaction

问题

死锁检测innodb_deadlock_detect,是每条事务都进行检测吗?

答:如果他要加锁访问的行上有锁,他才要检测。

结论

1.两阶段锁:在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放, 而是要等到事务结束时才释放。

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

3.发生死锁解决办法:

1.设置innodb_lock_wait_timeout参数,根据业务场景设置超时时间

2.开启死锁检测将innodb_deadlock_detect设置为on

4.热点行更新性能问题

1.关闭死锁检测(三思)

2.控制并发度,对应相同行的更新,进入引擎前进行排队

5.将热更新的行数据拆分成逻辑上的多行来减少锁冲突,但是业务复杂度可能会大大提高。

**innodb行级锁是通过锁索引记录实现的,如果更新的列没建索引是会锁住整个表的。
**

摘要内容来源:Mysql45讲--行锁功过