一,锁的种类

1.共享锁——Shared lock
又称读锁(S锁),共享锁不阻塞其他事务的读操作,但阻塞写操作,同一数据对象A可以共存多个共享锁,这被称为共享锁兼容。

当T1为数据对象A加上共享锁后,可以对A进行读操作,但不能进行写操作,并且T2可以再次对A加共享锁,大家都可以正常地读A,但是在A上的共享锁释放之前,任何事务不可以对A进行写操作。

例1:

T1:select * from table

T2:update table set column1=‘hello’

分析:假设T1先执行,则T2必须等待T1执行完才可以执行。因为T2为写操作,需要为table加一个排他锁,而数据库规定相同资源不可以同时存在共享锁和排他锁,所以T2必须等待T1执行完,释放掉共享锁,才可以加排他锁,然后执行update。

例2:(死锁的发生)

T1:

begin Transaction t1

select * from table with (holdlock) (holdlock的意思是加共享锁,直到事务结束(提交或回滚)才会释放)

update table set column1=‘hello’

T2:

begin Transaction t2

select * from table with (holdlock)

update table set column1=‘world’

分析:假设T1和T2同时到达select语句,都为table加上了共享锁,那么当T1、T2要执行update时,根据锁机制,共享锁需要升级为排他锁,但是排他锁与共享锁不能共存,要给table加排他锁,必须等待table上的共享锁全部释放才可以,可是holdlock的共享锁必须等待事务结束才能释放,因此T1和T2都在等待对方释放共享锁,形成循环等待,造成死锁。

例3:

T1:update table set column1=‘hello’ where id=‘001’

T2:update table set column1=‘world’ where id=‘002’

分析:此种情况有可能造成等待,分为id列有索引与无索引两种情况。

(1)id列有索引,则T1直接定位到id='001’行,加排他锁,更新;T2直接定位到id='002’行,加排他锁,更新。互不影响。

(2)id列无索引,T1扫描全表,找到id='001’行,加排他锁后,T2为了找到id='002’行,需要全表扫描,那么就会为table加共享锁或更新锁或排他锁,但不管加什么锁,都需要等待T1释放id='001’行的排他锁,不然无法为全表加锁。

死锁可以通过直接对表加排他锁来解决,即将事务的隔离级别提高至最高级——串行读,各个事务串行执行,可是这样虽然避免了死锁,但是效率太低了,那我们干脆别发明并发这个词语好了。

2.更新锁——Update lock
更新锁(U锁)。当T1给资源A加上更新锁后,代表该资源将在稍后更新,更新锁与共享锁兼容,更新锁可以防止例2里那种一般情况的死锁发生,更新锁会阻塞其他的更新锁和排他锁。因此相同资源上不能存在多个更新锁。

更新锁允许其他事务在更新之前读取资源。但不可以修改。因为其他事务想获取资源的排他锁时,发现该资源已存在U锁,则等待U锁释放。

在T1找到需要更新的数据时,更新锁直接转为排他锁,开始更新数据,不需要等待其他事务释放共享锁啥的。

那么就问了,共享锁为什么不可以直接升级为排他锁,而必须等待其他共享锁都释放掉才可以转为排他锁呢?

这就是共享锁和更新锁的一个区别了,共享锁之间是兼容的,但是更新锁之间互不兼容,因此仅有一个更新锁直接转为排他锁是安全的,而多个共享锁问也不问直接转为排他锁,那怎么行呢,排他锁只能有一个的,这就是为什么共享锁需要等待其他共享锁释放才可以升级为排他锁的原因了。

例4:

T1:

begin

select * from table with (updlock) (加更新锁)

update table set column1=‘hello’ (重点:这里T1做update时,不需要等T2释放什么,而是直接把更新锁升级为排他锁,然后执行update)

T2:

begin

select * from table (T1的更新锁不影响T2的select)

update table set column1=‘world’ (T2的update需要等待T1的update执行完)

分析:(1)T1先到达,T1的select句对table加更新锁,此时T2紧接着到达,T2的select句对table加共享锁,假设T2的select先执行完,要开始T2的update,发现table已有更新锁,则T2等,T1此时执行完select,然后将更新锁升级为排他锁,开始更新数据,执行完成,事务结束,释放排他锁,此时T2才开始对table加排他锁并更新。

(2)T2先到,T1紧接着,T2加共享锁 => T1加更新锁 => 假设T2先结束select => 试图将共享锁升级为排他锁 => 发现已有更新锁 => 之后的情况同

3.排他锁——Exclusive Locks
又叫独占锁,写锁,X锁,很容易理解,排他锁阻塞任何锁,假设T1为资源A加上排他锁,则其他事务不允许对资源A进行任何的读写操作。

例5:(假设id都是自增长且连续的)

T1: update table set column1=‘hello’ where id<1000

T2: update table set column1=‘world’ where id>1000

假设T1先达,T2随后至,这个过程中T1会对id<1000的记录施加排他锁.但不会阻塞T2的update。

例6:

T1: update table set column1=‘hello’ where id<1000

T2: update table set column1=‘world’ where id>900

假设T1先达,T2立刻也到,T1加的排他锁会阻塞T2的update。

4.意向锁——Intent Locks
意向锁,就是说当你给数据加锁时,必须先给他的上级加锁,用来向其他事务表明这段数据中的某些数据正在被加某某锁,你看着办吧。其实是一个节省开销的做法。

例7:

T1:

begin tran

select * from table with (xlock) where id=10 --意思是对id=10这一行强加排他锁

T2:

begin tran

select * from table with (tablock) --意思是要加表级锁

假设T1先执行,T2后执行,T2执行时,欲加表锁,为判断是否可以加表锁,数据库系统要逐条判断table表每行记录是否已有排他锁,

如果发现其中一行已经有排他锁了,就不允许再加表锁了。只是这样逐条判断效率太低了。

实际上,数据库系统不是这样工作的。当T1的select执行时,系统对表table的id=10的这一行加了排他锁,还同时悄悄的对整个表加了意向排他锁(IX),当T2执行表锁时,只需要看到这个表已经有意向排他锁存在,就直接等待,而不需要逐条检查资源了。

常用的意向锁有三种:意向共享锁(Intent Share Lock,简称IS锁);意向排他锁(Intent Exclusive Lock,简称IX锁);共享意向排它锁(Share Intent Exclusive Lock,简称SIX锁),共享意向排它锁的意思是,某事务要读取整个表,并更新其中的某些数据。

二、如何加锁

1.数据库自动加锁
其实锁在大多数情况下都是数据库自动加的,比如这么一条语句:

update table set column1=‘hello’

通过Profiler跟踪sql发现,他会逐行先获取U锁,然后转为X锁,更新完这一行,不释放X锁,继续获取下一行的U锁,转X…一直到全部更新结束,再逐行释放掉所有的X锁。

而如果加上where条件,如:update table set column1=‘hello’ where column2=‘world’,并且column2无索引,则逐行获取U锁,如果符合条件,转X锁,更新,不释放X锁;如果不符合,释放U锁。

但是如果有索引的话,则不需要逐行获取U锁 => 判断 => 转X锁或释放,而是直接获取到要更新行的X锁,更新,释放X锁即可。
所有的U,X,S之前都会首先为表或者页加意向锁。

2.手动加锁
例8:

T1:select * from table with (tablock) --对表加共享锁,且事务不完成,共享锁不释放。

T2:select * from table with (holdlock) --对涉及范围内加共享锁,事务不完成,共享锁不释放。

我感觉可能大多数情况下是不需要我们手动加锁的,因为我们不是专业搞数据库的,很多场景可能预想不到,就会导致一些错误,我们管理事务的隔离级别就可以了,数据库会根据隔离级别的不同,按照策略来加锁。

三、锁的粒度

锁的粒度指的是锁生效的范围,即行锁,页锁,或者是表锁。锁的粒度一般由数据库自主管理,不同的事物隔离级别,数据库会有不同的加锁策略(比如加什么类型的锁,加什么粒度的锁)。也可以手动指定。在下面的例子中,我们假设id是自增的主键。

例9:

T1::select * from table with (paglock) --页锁

T2::update table set column1=‘hello’ where id>10

T1执行时,会先对第一页加共享(S)锁,读完第一页后,释放锁,再对第二页加共享锁,依此类推。假设前10行记录恰好是一页(当然,一般不可能一页只有10行记录),那么T1执行到第一页查询时,并不会阻塞T2的更新。

例10:

T1:select * from table with (rowlock) --行锁

T2:update table set column1=‘hello’ where id=10

T1执行时,对每行加共享锁,读取,然后释放,再对下一行加锁;T2执行时,会对id=10的那一行试图加锁,只要该行没有被T1加上行锁,T2就可以顺利执行update操作。

例11:

T1:select * from table with (tablock) --表锁

T2:update table set column1=‘hello’ where id = 10

T1执行,对整个表加共享锁。T1必须完全查询完,T2才可以允许加锁,并开始更新。

通过分析以上3个例子可以得出结论:锁的粒度与系统的并发性和系统开销密切相关。粒度越小,则并发度越大,开销越大;反之,粒度越大,则并发度越小,开销越小。

四.锁与事务隔离级别的优先级

先上结论,手工指定的锁优先。

例12:

T1:GO

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

GO

BEGIN TRANSACTION

SELECT * FROM table with (NOLOCK)

GO

T2:update table set column1=‘hello’ where id=10

T1是事物隔离级别为最高级,串行读,数据库系统本应对后面的select语句自动加表级锁,但因为手工指定了NOLOCK,所以该select语句不会加任何锁,所以T2也就不会有任何阻塞。

五、数据库的几个 重要Hint及他们的区别

  1. holdlock 对表加共享锁,且事物不完成,共享锁不释放。

  2. tablock 对表加共享锁,只要statement不完成,共享锁不释放。

  3. TABLOCKX 对表加排他锁,事务不完成,排他锁不释放。

  4. xlock 加排他锁,和tablockx的区别:tablockx只能对整张表加锁,而xlock可以指定锁的粒度。

例13:

select * from table with (xlock paglock) --对page加排他锁

select * from table with (xlock tablock) --效果等同于select * from table with (tablockx)

六、如何提高并发效率

1.悲观锁
利用数据库本身的锁机制实现。通过上面对数据库锁的了解,可以根据具体业务情况综合使用事务隔离级别与合理的手工指定锁的方式比如降低锁的粒度等减少并发等待。

2.乐观锁
利用程序处理并发。原理都比较好理解,基本一看即懂。方式大概有以下3种:

(1)对记录加版本号。(2)对记录加时间戳。(3)对将要更新的数据进行提前读取、事后对比。首先说明一点的是:乐观锁在数据库上的实现完全是逻辑的,数据库本身不提供支持,而是需要开发者自己来实现。

常见的做法有两种:版本号控制及时间戳控制。

版本号控制的原理:

为表中加一个 version 字段;
当读取数据时,连同这个 version 字段一起读出;
数据每更新一次就将此值加一;
当提交更新时,判断数据库表中对应记录的当前版本号是否与之前取出来的版本号一致,如果一致则可以直接更新,如果不一致则表示是过期数据需要重试或者做其它操作(PS:这完完全全就是 CAS 的实现逻辑呀~)
至于时间戳控制,其原理和版本号控制差不多,也是在表中添加一个 timestamp 的时间戳字段,然后提交更新时判断数据库中对应记录的当前时间戳是否与之前取出来的时间戳一致,一致就更新,不一致就重试。

CAS是什么?
CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换。CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B。

CAS指令执行时,当且仅当内存地址V的值与预期值A相等时,将内存地址V的值修改为B,否则就什么都不做。整个比较并替换的操作是一个原子操作。

CAS是乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

数据库锁的详解, 共享锁, 更新锁, 排它锁, 意向锁, 加锁原理相关推荐

  1. sqlserver锁机制详解(sqlserver查看锁)

    简介 在SQL Server中,每一个查询都会找到最短路径实现自己的目标.如果数据库只接受一个连接一次只执行一个查询.那么查询当然是要多快好省的完成工作.但对于 大多数数据库来说是需要同时处理多个查询 ...

  2. 第八节:数据库层次的锁机制详解和事务隔离级别

    一. 基本概念 1.共享锁:(holdlock) (1). select的时候会自动加上共享锁,该条语句执行完,共享锁立即释放,与事务是否提交没有关系. (2). 显式通过添加(holdlock)来显 ...

  3. mysql中锁原理及for update悲观锁的详解

    mysql 中有多种多样的锁,今天我们具体分享一下: 一.mysql中乐观锁和悲观锁原理及种类: ​        乐观锁并不是数据库自带的,如果需要使用乐观锁,那么需要自己去实现,一般情况下,我们会 ...

  4. MySQL锁、事务隔离级别、MVCC机制详解、间隙锁、死锁等

    一. 简介 1. 锁定义 锁是计算机协调多个进程或线程并发访问某一资源的机制. 在数据库中,除了传统的计算资源(如CPU.RAM.I/O等)的争用以外,数据也是一种供需要用户共享的资源.如何保证数据并 ...

  5. Java多线程系列(十一):ReentrantReadWriteLock的实现原理与锁获取详解

    我们继续Java多线程与并发系列之旅,之前我们分享了Synchronized 和 ReentrantLock 都是独占锁,即在同一时刻只有一个线程获取到锁. 然而在有些业务场景中,我们大多在读取数据, ...

  6. mysql innodb 的锁机制_Mysql之Innodb锁机制详解

    InnoDB与MyISAM的最大不同有两点:一是支持事务(TRANSACTION):二是采用了行级锁.关于事务我们之前有专题介绍,这里就着重介绍下它的锁机制. 总的来说,InnoDB按照不同的分类共有 ...

  7. 高效并发:Synchornized的锁优化详解

    高效并发:Synchornized的锁优化详解 1. HotSpot虚拟机的对象头的内存布局 2. 偏向锁 举一反三:当锁进入偏向状态时,存储hash码的位置被覆盖了,那对象的hash码存储到哪儿的? ...

  8. php使用redis分布式锁,php基于redis的分布式锁实例详解

    在使用分布式锁进行互斥资源访问时候,我们很多方案是采用redis的实现. 固然,redis的单节点锁在极端情况也是有问题的,假设你的业务允许偶尔的失效,使用单节点的redis锁方案就足够了,简单而且效 ...

  9. java一个方法排他调用_Java编程实现排他锁代码详解

    一 .前言 某年某月某天,同事说需要一个文件排他锁功能,需求如下: (1)写操作是排他属性 (2)适用于同一进程的多线程/也适用于多进程的排他操作 (3)容错性:获得锁的进程若Crash,不影响到后续 ...

最新文章

  1. 【CLR的执行模型:将源代码编译成托管模块】
  2. 创建符合标准的、有语意的HTML页面——ASP.NET 2.0 CSS Friendly Control Adapters 1.0发布...
  3. 数据分析系列:绘制散点图(matplotlib)
  4. 区块链上智能合约的讲解
  5. java spring getbean_spring依赖注入中获取JavaBean
  6. 小米11系列有望提前亮相:最快年底相见
  7. 1000道Python题库系列分享四(40道)
  8. 【vue开发问题-解决方法】(九)使用element upload自定义接口上传文件,input多文件上传
  9. CSS margin合并
  10. 华为将全面支持鸿蒙,华为鸿蒙 2.0正式发布!明年华为手机将全面支持
  11. Matlab中单元数组和结构数组
  12. 【Flutter 问题系列第 26 篇】给 TextField 添加背景色,为什么没有效果 ?
  13. CentOS7常用软件安装配置说明
  14. linux环境启动tomcat成功后,访问链接一直在转圈
  15. CentOS 官网下载及版本说明
  16. 电商大数据应用之用户画像
  17. python中argument什么意思_Python中parameters与argument区别
  18. 手机的imei号的获取
  19. 转:vim的复制粘贴小结
  20. 上学易 APP - 小学信息平台,学区查询,幼升小,小学,学区,学校基础信息平台

热门文章

  1. java 设置数组长度_java如何增加数组长度
  2. U盘安装CentOS 7,简单有效
  3. Flink JobManager的HA原理分析
  4. 还不会老照片修复翻新?老照片修复软件哪个效果好?
  5. Django(一):了解web开发和URL+django的安装和简单使用
  6. HP F388经常卡纸
  7. 【iOS-Cocos2d游戏开发之四】独自收集Cocos2d提供的字体!共57种(有对照的字体图)
  8. java主线程等待所有子线程执行完毕再执行
  9. 正则表达式 匹配前缀字符串相符合
  10. 深入了解zipline