Mysql 中的锁

只有数据库层提供的锁机制才能真正保证数据访问排他性

Posted on November 18, 2016

  • 不同mysql存储引擎支持不同的锁机制

    InnoDB 支持行锁和表锁, 默认是行锁

    锁类型 行锁 页面锁 表锁
    开销 介于
    加锁效率 介于
    死锁可能 可能 可能 不会
    锁粒度 介于
    锁冲突概率 介于
    并发度 介于
  • show status like 'table%';

    如果Table_locks_waited的值比较高,则说明存在着较严重的表级锁争用情况


悲观锁

具有强烈的独占和排他特性

只有数据库层提供的锁机制才能真正保证数据访问的排他性

使用:

  1. 开始事务:

    begin;/begin work;/start transaction;

  2. 加悲观锁锁查询:

    select status from goods where id=1 for update;

  3. 修改:

    update...

  4. 提交事务:

    commit;/commit work;

在上述过程中, 对于被锁的实体(行或者表) SELECT ... FOR UPDATELOCK IN SHARE MODE需要等待该实体的事务结束, 才能继续进行, 对该实体的普通select不受影响

简单说: 写锁对写锁具有排他性


锁行/锁表

  • InnoDB默认Row-Level Lock,所以只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据)

    select * from goods where id=1 for update 有主键, 行锁

    如果主键不明确(TODO) 也会造成表锁

  • 如果明确指定索引, 也是行锁(TODO)

  • 否则MySQL 将会执行Table Lock (将整个数据表单给锁住)

    select * from goods where name='道具' for update 无主键, 表锁

悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。如果加锁的时间过长,其他用户长时间无法访问,影响了程序的并发访问性,同时这样对数据库性能开销影响也很大


共享锁和排他锁

共享锁:

select lock in share mode

  • 允许其他事务加共享锁, 但不允许其他事务去修改, 也不允许加排它锁

  • A事务添加了共享锁, B事务尝试普通更新, B将阻塞, 直到A提交

  • A, B都加共享锁后, A尝试更新, 将造成死锁

应用场景:

适合于两张表存在关系时的写操作, 如X, Y表有一致性的关系, 在对X已有记录x加共享锁, 然后insert 对应的y 到Y表

防止依赖数据被其他事务改变, 但本事务不需要改变依赖数据

排他锁

select for update

  • 不允许其他事务添加共享锁或者排它锁以及修改, 仅允许普通读取

  • A事务添加排他锁, B事务尝试普通更新, B阻塞直到A提交

  • A事务添加排他锁, B事务尝试添加任意锁, B阻塞直到A提交

应用场景:

本事务要改写记录, 防止其他事务修改


乐观锁

乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测

update t_goods
set status=2,version=version+1
where id=#{id} and version=#{version};

如果更新失败则重新获取version等数据, 重试.

不依赖数据库锁, 数据库压力小


锁与事务

事务可以保证操作的ACID原则, 解决ACID问题的两大技术点是:

  • 预写日志(Write-ahead logging) 保证原子性和持久性
  • 锁(locking) 保证各隔离性

这里并没有提到一致性,这是因为一致性是应用相关的话题,它的定义一般由业务系统来定义 - 什么样的状态才是一致的?而实现一致性的代码通常在业务层逻辑的代码中得以体现。


参考资料: