跳到主要内容

乐观锁与悲观锁

乐观锁

  • 用数据版本(Version)记录机制实现,是乐观锁最常用的一种实现方式
    为数据增加一个版本标识,一般通过为数据库表增加一个数字类型的 version 字段来实现,当读取数据时,将 version 字段一同读出,数据每更新一次,对 version 的值 +1,当提交更新的时候,判断数据库对应记录的当前版本信息和第一次取出来的是否相等,相等则更新,否则认为数据过期
    数据库并不实现乐观锁,需要用户自己实现

悲观锁

  • 在操作数据的时候认为此操作一定会出现数据冲突,因此每次操作都需要通过获取锁才能进行对相同数据的操作,与 Java 中的 synchronized 很相似,悲观锁需要耗费较多时间,悲观锁由数据库进行实现,使用时调用相关语句即可
  • 共享锁和排他锁是悲观锁的不同实现,都属于悲观锁的范畴
  • 使用悲观锁时,需要先关闭 MySQL 的自动提交属性,因为 MySQL 默认使用 autocommit 模式,执行一个更新操作后 MySQL 会立刻将结果进行提交
    set autocommit=0

共享锁

  • 共享锁又称读锁(read lock),是读取操作创建的锁,其他用户可以并发读取数据,但是任何事务都不能对数据进行修改(即获取数据上的排他锁),知道释放所有共享锁
    比如事务 T 对数据 A 加上共享锁后,其他事务只能对 A 再添加共享锁,不能加排他锁,获得共享锁的事务只能读取数据,不能修改数据
  • 在查询语句后添加 LOCK IN SHARE MODE,MySQL 会对查询结果中的每一行添加共享锁,当没有其他线程对查询结果集中的任何一行使用排他锁时,可以成功申请共享锁,否则会被阻塞
    其他线程可以读取使用了共享锁的表,而且这些线程读取的都是同一个版本的数据
    加上共享锁后,对于 updateinsertdelete 语句会自动加排他锁

排他锁

  • 排他锁(exclusive lock)又称写锁(writer lock),是悲观锁的一种实现方式
    假如事务 1 对数据对象 A 添加 X 锁,事务 1 可以读取 A 也可以修改 A,其他事务不能再对 A 加任何锁,直到事务 1 释放 A 上的锁。这样保证了其他事务在事务 1 释放 A 上的锁之前不能再读取和修改 A,排他锁会阻塞所有的排他锁和共享锁
    读取加读锁的原因:防止数据在被读取的时候被其他线程加上写锁
    使用排他锁:在 SQL 语句后面添加 for update

行锁

  • 行锁又分共享锁(S)和排他锁(X),就是给某一行(某一条记录)加上锁

    行级锁都是基于索引的,如果一条 SQL 用不到索引是不会使用行级锁的,而会使用表级锁

  • InnoDB 中还有两种意向锁,这两种意向锁都是表级锁,分别是意向共享锁(IS)意向排他锁(IX),事务在添加行级的共享锁和排他锁之前需要先获取意向排他锁,这样在 InnoDB 中就实现了行级和表级多粒度锁共存
  • 优点:锁定的粒度小,发生锁冲突的可能性低,并发度高
    缺点:开销大,加锁慢,会出现死锁
  • 场景:适用于有大量按照索引条件并发更新少量不同数据,同时又有并发查询需求的情况

表锁

  • InnoDB 中的行锁在使用索引的情况(即通过索引检索数据时)下使用,没有使用索引时使用表锁
  • 优点:开销小,加锁快,不会出现死锁
    缺点:锁定粒度大,发生锁冲突的可能性大,并发度低
  • 场景:适合以查询为主,少量按照索引条件更新数据的情况

页面锁

  • 开销和加锁的时间介于表锁和行锁之间,会出现死锁的情况,锁定粒度介于行锁和表锁之间,并发度中等

死锁

  • 死锁(Deadlock):是指两个或两个以上的进程在执行过程中因争夺资源造成的一种互相等待的现象,在没有外力作用的情况下,进程都将无法继续执行,此时系统处于死锁状态(产生了死锁),这些永远在互相等待的进程称为死锁进程。