Contents
  1. 1. 并发控制
  2. 2.
  3. 3. 锁的分类(oracle)
  4. 4. 乐观锁和悲观锁
    1. 4.1. 悲观锁
    2. 4.2. 悲观锁的实现流程
    3. 4.3. 乐观锁
  5. 5. 共享锁、排它锁和更新锁
  6. 6. 参考链接

本文转自blog

在并发访问下,数据库可能会出现脏读、不可重复读和幻读等读现象,主流数据库为了应对这些问题,引入了事务隔离级别的概念。

并发控制

在计算科学,特别是程序设计、操作系统、多处理机和数据库等领域,并发控制(Concurrency control)是确保及时纠正并由并发操作导致的错误的一种机制。

数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时,不破坏事务的隔离性、统一性以及数据库的统一性。

封锁、时间戳、乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。

当并发事务同时访问一个资源时,有可能导致数据不一致,因此需要一种机制来将数据访问顺序化,以保证数据库数据的一致性,锁就是其中的一种机制。

在计算机科学中,锁是在执行多线程时用于强行限制资源访问的同步机制,即用于在并发控制中保证对互斥要求的满足。

锁的分类(oracle)

  1. 操作划分为DML锁DDL锁
  2. 锁的粒度分为表级锁行级锁页级锁(MySQL)
  3. 锁的级别分为共享锁排他锁
  4. 加锁方式分为自动锁显示锁
  5. 使用方式分为乐观锁悲观锁

乐观锁和悲观锁

悲观锁

在关系型数据库管理系统里,悲观并发控制(又名“悲观锁”,Pessimistic Concurrency Control,PCC)是一种并发控制的方法。它可以阻止一个事务以影响其他用户的方式来修改数据,如果一个事务执行的操作都对某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作。其主要应用场景是数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。

悲观锁(Pessimistic Lock),就是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改总是保持悲观态度,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。
悲观锁的实现,往往依赖数据库提供的锁机制(也只有数据库层提供的锁机制才能保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)

悲观锁的实现流程

  1. 在对任意记录进行修改前,先尝试为该记录加上排它锁(exclusive locking)

  2. 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。具体响应方式由开发者根据实际需要决定。

  3. 如果成功加锁,那么就需要对记录做修改,事务完成后就会解锁了

  4. 期间如果有其他对该记录做修改或者加排它锁的操作,都会等待我们解锁或直接抛出异常

Java synchronized 就属于悲观锁的一种实现,每次线程要修改数据时都先获得锁,保证同一时刻只有一个线程能操作数据,其他线程则会被block

乐观锁

在关系型数据库管理系统里,乐观并发控制(又名“乐观锁”,Optimistic Concurrency Control,OCC)是一种并发控制的方法。它假设多用户并发的事务在处理时不会彼此相互影响,各事务能够在不产生锁的情况下处理各自影响的那部分数据。在提交数据更新之前,每个事务会会检查在该事务读取数据后,有没有其他事务又修改了该数据。如果存在,正在提交的事务会进行回滚操作(Roll Back)。其应用场景是读多写少的应用场景,这样可以提高吞吐量。

乐观锁(Optimistic Lock),就是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改总是保持乐观态度,每次去拿数据的时候都认为别人不会修改,所以每次在拿数据的时候都不会上锁,但是在提交更新的时候会判断一下在此期间别人有没有去更新这个数据,如果发现冲突了,则返回用户错误的信息,让用户决定如何处理。
相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。

“数据版本,为数据增加一个版本标识,当读取数据时,将版本标识的值一同读出,数据每更新一次,同时对版本标识进行更新。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出数据时的版本标识进行比对,如果数据库表当前版本号与第一次取出来的版本标识值相等,则予以更新,否则认为是过期数据。”

实现数据版本的方式:

  1. 使用版本号(Version):即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。
  2. 使用时间戳:乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

Java JUC(java.util.concurrent)中atomic包就是乐观锁的一种实现,AtomicInteger通过CAS(Compare And Set)操作实现线程安全的自增

共享锁、排它锁和更新锁

  • 共享锁(S):又称为读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。(因此,共享锁与共享锁相容)。
  • 排他锁(X):又称为写锁。若事务T对数据对象A加上X锁,则只允许T读取和修改A,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。这就保证了其他事务在T释放A上的锁之前不能再读取和修改A。
  • 更新锁(U):用来预定要对此页施加X锁,它允许其他事务读,但不允许再施加U锁或X锁;当被读取的页将要被更新时,则升级为X锁;U锁一直到事务结束时才能被释放。

参考链接

  1. http://www.hollischuang.com/archives/909
  2. https://www.jianshu.com/p/f5ff017db62a
Contents
  1. 1. 并发控制
  2. 2.
  3. 3. 锁的分类(oracle)
  4. 4. 乐观锁和悲观锁
    1. 4.1. 悲观锁
    2. 4.2. 悲观锁的实现流程
    3. 4.3. 乐观锁
  5. 5. 共享锁、排它锁和更新锁
  6. 6. 参考链接