前言
行锁是针对行记录的锁,相比较于表级别锁,行锁的颗粒度更细,更加灵活;
目录
- 行锁的介绍
- 上锁和释放锁的时机
- 死锁问题
正文
1. 行锁的介绍
行锁的实现是在引擎层面,比如我们常用的InnoDB就是支持行锁的;但是MyISAM却不支持;
像我们之前介绍的表锁,是把整个表锁住,这种方式最大的缺点就是效率低,如果有一个线程在操作表,那么其他线程都只能等;
但是行锁不一样,它是针对行的索引进行上锁,如果有一个线程在操作这行数据,那么其他线程也只是这行记录不能操作,但是表里面的其他记录还是可以同步进行的,这样效率就会高很多;
如果表没有索引,那么行锁会锁住整个表
2. 上锁和释放锁的时机
上锁是在事务启动时(即第一条语句开始时),而释放锁是在事务提交时;
这两个阶段也叫做两阶段锁协议;
下面我们用例子看一下,开启两个线程,一个先执行更新语句,另一个执行接着也执行更新语句;
可以看到,第一个线程可以顺利完成,但是第二个线程再执行更新操作就会卡住;
这里的上锁就是在第一个线程执行更新操作时上的,接着第二个线程也去更新同一条数据(索引id=1);
此时线程1获得的行锁(id=1)还没释放,所以线程2就会被阻塞;
如果阻塞时间过长,会报错如下:
1205 - Lock wait timeout exceeded; try restarting transaction
这里对应的超时参数是innodb_lock_wait_timeout
,默认50S,即超过50S还是阻塞的,就直接报错退出了;
如果在超时时间内,线程1提交了事务,那么线程2就可以继续执行了;
3. 死锁问题
因为行锁是针对行记录的锁,所以多个线程可以同时锁住不同的行;
但是如果两个线程各持有一个行锁,还想要获取对方的行锁,那么此时就会导致死锁;
复现:
我们可以开启两个线程,线程1更新id=1,线程2更新id=2,然后再互调更新操作;
如下所示,现在两个线程各自持有id=1的行锁和id=2的行锁;
此时我们让线程1再去获取id=2的行锁,会被锁住;
然后再让线程2去获取id=1的行锁,此时会提示出现了死锁,如下所示:
可以看到,默认的死锁处理方式,就是重启后面的事务,以此来释放一个行锁,然后让前面的事务可以继续执行;
这个就是死锁检测机制:如果有发现死锁,则回滚死锁事务链中的某个事务,使得其他事务可以继续执行;
上面的例子就是线程2中的事务被强制重启,此时线程2中的行锁就会被释放,线程1中的事务会被继续执行;
我们可以通过设置参数:innodb_deadlock_detect,来开启和关闭死锁检测机制,默认on;
总结
行锁是针对行记录的锁,在事务启动时(第一条SQL语句执行时)申请锁,在事务提交时释放锁;
死锁就是多个线程各自拥有行锁,然后又想要获取对方的锁,此时就会造成行锁;
默认的死锁检测机制会自动检测是否有死锁,如果有发现死锁,则回滚死锁事务链中的某个事务,使得其他事务可以继续执行;