锁分类

  • 根据操作粒度可分为:表级锁和行级锁
  • 表级锁: 每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。
  • 行级锁: 每次操作锁住一行数据。锁定粒度小,发生锁冲突的概率最低,并发度最高。
  • 根据操作类型可分为:读锁写锁属于行级锁
  • 读锁(S锁): 又称共享锁,针对同一份数据,多个读操作可以同时进行,互不影响。
  • 写锁(X锁): 又称排他锁,当前写操作没有完成时,会阻断其他读锁和写锁。
  • 意向读锁(IS锁)意向写锁(IX锁) 属于表级锁;在对表记录添加S锁或X锁前,会先对表添加IS锁或IX锁。
    S锁: 事务A对记录添加了S锁,可读不可写,其他事务可以对该记录追加S锁,但是不能追加X锁,如果想添加X锁,需要等待所有S锁释放。
    X锁: 事务A对记录添加了X锁,可读可写,其他事务不能读也不能写。
  • 根据操作性能可分为:乐观锁和悲观锁
  • 悲观锁:
  • 乐观锁:

行锁原理

  • InnoDB行锁是通过对索引数据页上的记录加锁实现的,主要实现算法有3种:
    Record Lock锁:行锁,记录锁 ; 针对主键或唯一索引进行查询的时候,mysql会对该行数据进行加锁,避免其他事务进行修改.锁定单个行记录的锁。(记录锁,RC(读已提交)、RR(可重复读)隔离级别都支持)
    Gap Lock锁:间隙锁 ; 索引是基于B+ Tree的结构存储,当间隙锁锁定索引列的时候,默认锁定该索引列的前后开区间的范围(左开右闭),确保索引记录的间隙不变.在基于索引列的范围查询中,无论是否为唯一索引,都会自动加一个间隙锁(范围锁,RR(可重复读)支持
    Next-key Lock锁:临键锁;行锁和间隙锁的结合;锁住记录的同时,并且锁住数据前后的范围(左开右闭).当查询时是否使用唯一索引进行查询,mysql都会加上临键锁(RR(可重复读)支持
  • 在RR(可重复读)隔离级别,InnoDB对记录加锁都是先进行Next-key Lock,当检查到SQL操作含有唯一索引,InnoDB会对Next-key Lock进行优化,降级为Record Lock。
  • select … from :InnoDB引擎采用MVCC机制实现非阻塞读,所以对普通的select语句不会加锁
  • select … from lock in share mode:追加了共享锁,InnoDB会先进行Next-key Lock锁处理,如果扫描发现唯一索引,将降级为Record Lock锁。
  • select … from for update:追加排他锁,InnoDB会先进行Next-key Lock锁进行处理,如果扫描发现唯一索引,将降级为Record Lock锁。
  • update … where …:如果在 where 后扫描发现唯一索引,将降级为Record Lock锁。
  • delete … where …:如果在 where 后扫描发现唯一索引,将降级为Record Lock锁。
  • insert:InnoDB会在将要插入的那一行设置排他的Record Lock锁。

悲观锁

  • 悲观是指在数据处理过程,将数据处于锁定状态。从广义上来讲,行锁、表锁、写锁、共享锁、排他锁等,均属于悲观锁范畴。
  • 表级锁
    表级锁每次操作都锁住整张表,并发度最低。命令:
    手动增加表锁:lock table 表名称 read|write,表名称2 read|write;
    查看表上加过的锁:show open tables;
    删除表锁:unlock tables;
    表级读锁:当前表使用read锁,当前连接和其他连接都可以进行读操作;当前连接执行增删改时会报错,其他连接增删改时会阻塞;其他连接可追加read锁;其他连接追加write锁将被阻塞。
    表级写锁:当前表使用write锁,当前连接可以进行增删改查操作;其他连接增删改查将会被阻塞;其他连接追加read或write锁均被阻塞。
  • 行级锁
  • 共享锁(S锁,读锁)
    共享锁就是多个事务对于同一个数据可以共享一把锁,都能查看数据,但是不能增删改。
    命令:select … from … where id = … lock in share mode;只适用于select语句
    总结: 当前连接使用共享锁时,其他连接也可以使用共享锁;当前连接开启事务并使用共享锁时,其他未开启连接进行更新操作时会被阻塞;当两个或多个连接均开始事务后,其中一个先进行更删改时,当前连接会被阻塞,当其他连接进行增删改时,会报错,第一个进行更删改的连接根据最后的commit或rollback决定是否提交更删改操作,在这之前,其他窗口的查询均是第一个连接更删改前的数据。
  • 排他锁(X锁,写锁)
    排他锁就是一个事务获取了一个数据并加上了排他锁,其他事务事务就不能对改行记录做其它操作,也不能获取该行的锁。
    命令:select … from … where id = … for update;InnoDB引擎默认将update,delete增加 for update 命令
    总结: 当两个或多个连接开启事务时,第一个连接进行更新操作,第二个连接进行更新操作后会被阻塞,如果长时间阻塞后会报超时错误;如果第一个连接进行了commit操作后,第二个连接阻塞会被唤醒,并执行更新操作,如果第二个连接执行commit操作,会将第一个连接的数据覆盖。
    当一个事务开启了排他锁,该事物可以进行增删改查,其他事务不能修改,也不能获取排他锁。

行级锁是依赖于索引的,所以当操作没有用到索引时,全表会被锁定,包括间隙锁,其他事务进行增删改时会被阻塞,可以查询但是不能获取排他锁。

乐观锁

  • 乐观锁相对于悲观锁而言,它不是数据库提供的功能,而是需要开发者去实现,乐观锁在处理数据时是不加锁的,只有在事务进行提交的时候判断是否有冲突。
  • 乐观锁和悲观锁都能解决数据库的写写问题;但是乐观锁并发处理能力更高。
  • 乐观锁实现原理
  • 使用版本字段(version)
    先给表中增加版本(version)字段,每操作一次,将版本号加1。每次提交事务会和现有的version对比,大于等于说明数据被其他事务修改过了。
  • 使用时间戳(Timestamp)/ˈtaɪmstæmp/
    与version相似,也需要给数据表增加一个字段,字段类型使用timestamp时间戳。在提交更新的时候检查当前数据库中数据的时间戳和自己更新前渠道的时间戳对比,如果一致提交更新,否则就是版本冲突,更新失败。

除了手动实现乐观锁外,许多框架也封装了乐观锁的实现,比如hibernate框架;MyBatis框架可以使用OptimisticLocker插件来扩展实现