表锁

加锁后需要主动释放 unlock tables
S锁, 也叫做读锁, 共享锁, 对应于我们常用的 lock table table_name read
X锁, 也叫做写锁, 排他锁, 对应于我们常用的 lock table table_name write

行锁

事务提交后, 会自动释放锁
S锁, 也叫做读锁, 共享锁, 对应于我们常用的 select from users where id =1 lock in share mode
X锁, 也叫做写锁, 排他锁, 对应于我们常用的 select
from users where id =1 for update

S X
S 兼容 冲突
X 冲突 冲突

X锁又分为: 行锁(record lock), 间隙锁(gap lock), 临界锁(next-key lock)
在做更新操作时(insert, update, delete, select for update), mysql会对扫描到的记录加行锁
除了通过唯一索引等值匹配, mysql还会对扫描到的行加间隙锁, 此时就形成了临界锁(行锁 + 间隙锁)

死锁案例一

伪代码

  1. @Transactional
  2. public void upsert(Entity entity) {
  3. Entity exists = mapper.select(entity.getId())
  4. if(exists != null) {
  5. mapper.update(entity);
  6. return;
  7. }
  8. try {
  9. mapper.insert(entity);
  10. } catch(DuplicateKeyException e) {
  11. mapper.update(entity)
  12. }
  13. }

复现步骤

  1. 目标记录在数据库中不存在
  2. 3个线程同时执行该方法, 并且在执行第3行代码时, 查出来的记录都是空
  3. 进而, 3个线程都尝试向数据库中插入目标记录, 这时只有一个线程会执行成功, 并结束返回
  4. 另外2个线程则会插入失败, 捕获到重复键异常, 这时它们都会对目标记录加上S锁
  5. 之后, 这2个线程又会去更新目标记录, 在更新的之前, 会先申请目标记录的X锁, 此时就出现了死锁

    查看锁相关信息

    1. show engine innodb status;
    2. select * from information_schema.innodb_trx;
    3. select * from information_schema.innodb_locks;
    4. select * from information_schema.innodb_lock_waits;