数据库锁机制及乐观锁,悲观锁的并发控制 3.乐观锁并发控制 4.悲观锁并发控制

1.数据库锁的种类

  ① 共享锁

    行级锁,共享锁是在执行select操作时使用的锁机制.

      共享锁与共享锁共存,即当一个事务正在对A表进行查询操作时,另一个事务同样可以对A表进行查询操作,演示如下:

1 T1:  select * from A;(加共享锁A)
2 T2:  select * from A;(加共享锁B)
3 -- 此时T1,T2可同时执行

  ② 排它锁

    行级锁,排它锁是在执行update,delete等对数据有修改操作时使用的锁.

    排它锁不与排它锁以及共享锁共存,即当一个事务正在对A表进行更新操作时,会对操作的数据加上排它锁,其他事务无法访问更无法修改被加锁的数据行,演示如下:

1 T1: update A set a=1 where id<100;
2 T2: update A set a=2 where id>100;
3 T3: update A set a=3 where id=50;
4 T4: select * from A where id<50;

    当T1先到达时,会对id<100的数据加上排它锁,其他事务无法访问这些数据,由于T2访问的是id>100的数据,改数据未被加上排它锁,所以T2能够对id>100的数据加排它锁,

    但是T3访问的数据已经被T1加上排它锁,除非T1执行完毕释放排它锁,T3才能访问id=50的数据.T4跟T3同理.

    排它锁的并发性低下,请看下面这个例子:

1 T1:update table set column1='hello' where id=10
2 T2:update table set column1='world' where id=20

    该例子有两种情况:

    第一种情况:如果id是主键(有主键索引)或者有索引的一列,T1会一下子找到id=10的那一列,并对该列加上排它锁.T2同理,这两个事务互不干涉,能够并发执行

    第二种情况:如果id是普通的列,且没有索引,那么当T1搜索到id=10这一列并对他加上排它锁之后,由于T2需要全表扫描找到id=20这一列,但是由于T1对id=10这一列加上了排它锁,T2无法访问,就会进入阻塞状态,试想如果T1是一个较大的事务,那么T2一直得不到想要的资源,严重影响用户体验,尤其是在web应用这种对程序的效率要求较高的应用.

    排它锁的并发性低下,但是更新锁能够比较好的解决这个问题.

  ③ 更新锁

     行级锁,被加上更新锁的数据是可能被修改的数据.更新锁与排它锁及更新锁互斥,与共享锁共存.演示如下:

1 T1: select * from A;(加更新锁)
2 T2: select * from A;(加更新锁)
3 T3: select * from A;(加共享锁)
4 T4: update A set a=1;(加排它锁)

    当T1到达时,对A表加更新锁,然后T2到达,企图对A表加更新锁,但是发现表A已经被加上更新锁,其阻塞.然后T3到达,对A表加共享锁,由于共享锁与更新锁共存,所以T3能够加上共享锁并能够正常读取,然后T4到达,由于排它锁与更新锁互斥,所以T4阻塞.以下是另一个例子:

1 T1:
2 select * from A;(加更新锁)
3 update set a=1 where id=1;
4 T2:
5 select * from A;(加共享锁)
6 update set a=1 where id=1;

    T1先到达执行查询操作,同时T2到达,T2开始查询操作,两者可以共存.

    第一种情况:假设T2查询操作先结束,准备执行更新操作对相应行上锁时,发现该表有了更新锁,所以该操作阻塞.当T1查询操作结束后,执行更新操作,更新锁自动升级为排它锁,T1更新结束之后,释放锁,T2开始更新.

    第二种情况:假设T1查询操作先结束,准备执行更新操作,更新锁升级为排它锁,由于排它锁与共享锁不同存,T2的查询操作被阻塞,等T1的更新操作结束之后,T2才能继续操作.

  ④ 意向锁

     表级锁,意向锁就是反应当前表是否有锁的锁机制.

     如果T1正在对A表的一部分数据进行更新操作(加了行级锁),这个时候另一个事务T2需要对A表加表级锁,由于表如果存在行级锁之后无法再添加表级锁,它会对全表进行扫描判断表的每一行是否存在排它锁,这样效率低下.意向锁就会能够防止这种情况的发生,当A表有一行被加上了排它锁之后,A表会自动被加上意向排它锁,那么其他事务需要加表级锁的事务只需要判断A表是否有意向锁就行了.

1 T1: update A set a=1 where id=1;(加行级排它锁)
2 T2: update A set a=1 where id=1;(加表级排它锁)

    当T1执行更新操作时,对id=1这一行加上行级锁之后,数据库会自动对A表加上一个意向排它锁,当T2到达之后,T2会判断A表是否有意向排它锁,如果存在则阻塞,不存在则对A表加上表级排他锁.

     ⑤ 小tips

    在操作数据库时,数据库会自动为操作的数据添加合适的锁,这就是为什么不需要懂得数据库的锁机制我们依然可以愉快的写SQL.不同的存储引擎支持的锁机制不同,根据需要可选择不同的存储引擎.

    程序员可手动对对象加锁,这里以我熟悉的mysql数据库为例:这里我使用的是mysql默认的innodb存储引擎,该引擎支持行级锁,表级锁.InnoDB行锁是通过给索引上的索引项加锁来实现的,如果不存在索引,则对整张表加锁.

    添加表级锁如下:      

1 LOCK table admin write; -- 对admin加表锁
2 unlock tables;-- 释放当前session具有的表锁

      对表添加了写锁之后,是无法对其他表进行操作的,所有事务必须在一开始获取自己需要的所有锁,获取了写锁之后能进行写与读操作,获取了读锁之后,只能进行读操作

     添加行级锁如下:

1 select * from admin for update; -- 获得排它锁

    更多关于mysql的锁机制请自行看官方文档.

2.乐观锁与悲观锁

  乐观锁与悲观锁是为了保持事务的隔离性以及数据库的一致性而被业内定义出来的一种手段,并不属于数据库锁机制,但是悲观锁是依赖于数据库的锁机制实现的.

    ① 乐观锁

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

    乐观锁认为其他事务都不会修改自己的正在操作的数据,所以不会使用数据锁机制(数据库锁机制无法关闭,这里说的不使用是不会像悲观锁那样显示加锁,悲观锁操作见第4点)..

    ② 悲观锁

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

      悲观锁的实现主要依赖于数据库的锁机制,会显示的使用锁.

  乐观锁并发控制常用方法是使用一个版本号控制手段.即在表中添加一个版本号version字段,当有一个事务对该数据进行操作时会取得版本号,当事务对数据更新时会判断数据库中的version是否跟自己取得version一致,一致则说明该数据未被修改过,否则说明数据被修改过,更新操作失败.更新成功则对version字段加一.

T1: 
select * from A;
update A set a=1,version=version+1 where id=1 and version=vs;
T2:
select * from A;
update A set a=2,version=version+1 where id=1 and version=vs;
-- vs为事务取得的版本号

    假设T1,T2并发的对A表进行操作,T1,T2取得的version字段值相同,T1事务先执行完,T1执行完之后version被加一,T2在提交更新时,因为version字段已经被修改,所以更新失败.

    乐观锁不适用于更新操作较多的情况,如果更新操作较多,会出现很多更新失败的情况,需要大量回滚,重新操作,程序效率大幅度降低.但是他的查询并发度高.

4.悲观锁并发控制

  悲观锁并发控制主要通过对操作的数据加锁实现.下面这个例子是通过加排它锁实现:

select * from A where id=1 for update

  在操作A表示,对id=1的行加上行级锁,其他事务无法操作该数据.实现了悲观锁.

  悲观锁并发度低,每次都需要对表上锁,但是能够保证数据的安全.