数据库锁简析

  • 数据库大并发操作要考虑死锁和锁的性能问题。
  • 用T1代表一个数据库执行请求,T2代表另一个请求,
  • 也可以理解为T1为一个线程,T2 为另一个线程。
  • T3,T4以此类推。下面以SQL Server为例。

锁的种类

共享锁(Shared lock)

  • 例1:
  • T1: select * from table (请想象它需要执行1个小时之久,后面的sql语句请都这么想象)
  • T2: update table set column1='hello'
  • 过程:
  • T1运行 (加共享锁)
    T2运行
    If T1 还没执行完
    T2等......
    else
    锁被释放
    T2执行
    endif
  • T2之所以要等,是因为T2在执行update前,试图对table表加一个排他锁,
  • 而数据库规定同一资源上不能同时共存共享锁和排他锁。所以T2必须等T1
  • 执行完,释放了共享锁,才能加上排他锁,然后才能开始执行update语句。
  • 例2:
  • T1: select * from table
  • T2: select * from table
  • 这里T2不用等待T1执行完,而是可以马上执行。
  • 分析:
  • T1运行,则table被加锁,比如叫lockA
  • T2运行,再对table加一个共享锁,比如叫lockB。
  • 两个锁是可以同时存在于同一资源上的(比如同一个表上)。
  • 这被称为共享锁与共享锁兼容。
  • 这意味着共享锁不阻止其它session同时读资源,
  • 但阻止其它session update
  • 例3:
  • T1: select * from table
  • T2: select * from table
  • T3: update table set column1='hello'
  • 这次,T2不用等T1运行完就能运行,
  • T3却要等T1和T2都运行完才能运行。
  • 因为T3必须等T1和T2的共享锁全部释放才能进行加排他锁然后执行update操作
  • 例4:(死锁的发生)
  • T1:
    begin tran
    select * from table (holdlock) (holdlock意思是加共享锁,直到事务结束才释放)
    update table set column1='hello'T2:
    begin tran
    select * from table(holdlock)
    update table set column1='world'
  • 假设T1和T2同时达到select,
  • T1对table加共享锁,T2也对加共享锁,
  • 当T1的select执行完,准备执行update时,
  • 根据锁机制,
  • T1的共享锁需要升级到排他锁才能执行接下来的update.在升级排他锁前,
  • 必须等table上的其它共享锁释放,
  • 但因为holdlock这样的共享锁只有等事务结束后才释放,
  • 所以因为T2的共享锁不释放而导致T1等
  • (等T2释放共享锁,自己好升级成排他锁),
  • 同理,也因为T1的共享锁不释放而导致T2等。死锁产生了。
  • 例5:
  • T1:
    begin tran
    update table set column1='hello' where id=10T2:
    begin tran
    update table set column1='world' where id=20
  • 这种语句虽然最为常见,
  • 很多人觉得它有机会产生死锁,
  • 但实际上要看情况,
  • 如果id是主键上面有索引,
  • 那么T1会一下子找到该条记录(id=10的记录),
  • 然后对该条记录加排他锁,T2,同样,一下子通过索引定位到记录,
  • 然后对id=20的记录加排他锁,这样T1和T2各更新各的,互不影响。T2也不
  • 需要等。
  • 但如果id是普通的一列,没有索引。
  • 那么当T1对id=10这一行加排他锁后,
  • T2为了找到id=20,需要对全表扫描,
  • 那么就会预先对表加上共享锁或更新锁或排他锁
  • (依赖于数据库执行策略和方式,比如第一次执行和第二次执行数据库执行策略就会不同)。
  • 但因为T1已经为一条记录加了排他锁,
  • 导致T2的全表扫描进行不下去,就导致T2等待

死锁怎么解决呢?一种办法是,如下:

例6:

T1:

begin tran

select * from table(xlock) (xlock意思是直接对表加排他锁)

update table set column1='hello'

T2:

begin tran

select * from table(xlock)

update table set column1='world'

这样,当T1的select 执行时,直接对表加上了排他锁,T2在执行select时,就需要等T1事物完全执行完才能执行。排除了死锁发生。

但当第三个user过来想执行一个查询语句时,也因为排他锁的存在而不得不等待,第四个、第五个user也会因此而等待。在大并发

情况下,让大家等待效果可想而知,所以,这里引入了更新锁。

更新锁(Update lock)

为解决死锁,引入更新锁。

例7:

----------------------------------------

T1:

begin tran

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

update table set column1='hello'

T2:

begin tran

select * from table(updlock)

update table set column1='world'

更新锁的意思是:“我现在只想读,你们别人也可以读,但我将来可能会做更新操作,我已经获取了从共享锁(用来读)到排他锁

(用来更新)的资格”。一个事务只能有一个更新锁获此资格。

T1执行select,加更新锁。

T2运行,准备加更新锁,但发现已经有一个更新锁在那儿了,只好等。

当后来有user3、user4...需要查询table表中的数据时,并不会因为T1的select在执行就被阻塞,照样能查询,相比起例6,这提高

了效率。

例8:

----------------------------------------

T1: select * from table(updlock) (加更新锁)

T2: select * from table(updlock) (等待,直到T1释放更新锁,因为同一时间不能在同一资源上有两个更新锁)

T3: select * from table (加共享锁,但不用等updlock释放,就可以读)

这个例子是说明:共享锁和更新锁可以同时在同一个资源上。这被称为共享锁和更新锁是兼容的。

例9:

----------------------------------------

T1:

begin

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

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

T2:

begin

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

update table set column1='world' (T2的update需要等T1的update做完才能执行)

我们以这个例子来加深更新锁的理解,

第一种情况:T1先达,T2紧接到达;在这种情况中,T1先对表加更新锁,T2对表加共享锁,假设T2的select先执行完,准备执行update,

发现已有更新锁存在,T2等。T1执行这时才执行完select,准备执行update,更新锁升级为排他锁,然后执行update,执行完成,事务

结束,释放锁,T2才轮到执行update。

第二种情况:T2先达,T1紧接达;在这种情况,T2先对表加共享锁,T1达后,T1对表加更新锁,假设T2 select先结束,准备

update,发现已有更新锁,则等待,后面步骤就跟第一种情况一样了。

这个例子是说明:排他锁与更新锁是不兼容的,它们不能同时加在同一子资源上。

排他锁(独占锁,Exclusive Locks)

这个简单,即其它事务既不能读,又不能改排他锁锁定的资源。

例10

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。

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

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

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

如同例10,T1先达,T2立刻也到,T1加的排他锁会阻塞T2的update.

意向锁(Intent Locks)

意向锁就是说在屋(比如代表一个表)门口设置一个标识,说明屋子里有人(比如代表某些记录)被锁住了。另一个人想知道屋子

里是否有人被锁,不用进屋子里一个一个的去查,直接看门口标识就行了。

当一个表中的某一行被加上排他锁后,该表就不能再被加表锁。数据库程序如何知道该表不能被加表锁?一种方式是逐条的判断该

表的每一条记录是否已经有排他锁,另一种方式是直接在表这一层级检查表本身是否有意向锁,不需要逐条判断。显然后者效率高。

例12:

----------------------------------------

T1: begin tran

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

T2: begin tran

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

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

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

实际上,数据库系统不是这样工作的。当T1的select执行时,系统对表table的id=10的这一行加了排他锁,还同时悄悄的对整个表

加了意向排他锁(IX),当T2执行表锁时,只需要看到这个表已经有意向排他锁存在,就直接等待,而不需要逐条检查资源了。

例13:

----------------------------------------

T1: begin tran

update table set column1='hello' where id=1

T2: begin tran

update table set column1='world' where id=1

这个例子和上面的例子实际效果相同,T1执行,系统对table同时对行加排他锁、对页加意向排他锁、对表加意向排他锁。

计划锁(Schema Locks)

例14:

----------------------------------------

alter table .... (加schema locks,称之为Schema modification (Sch-M) locks

DDL语句都会加Sch-M锁

该锁不允许任何其它session连接该表。连都连不了这个表了,当然更不用说想对该表执行什么sql语句了。

例15:

----------------------------------------

用jdbc向数据库发送了一条新的sql语句,数据库要先对之进行编译,在编译期间,也会加锁,称之为:Schema stability (Sch-S) locks

select * from tableA

编译这条语句过程中,其它session可以对表tableA做任何操作(update,delete,加排他锁等等),但不能做DDL(比如alter table)操作。

Bulk Update Locks 主要在批量导数据时用(比如用类似于oracle中的imp/exp的bcp命令)。不难理解,程序员往往也不需要关心,不赘述了。

3 何时加锁?

如何加锁,何时加锁,加什么锁,你可以通过hint手工强行指定,但大多是数据库系统自动决定的。这就是为什么我们可以不懂锁也可

以高高兴兴的写SQL。

例15:

----------------------------------------

T1: begin tran

update table set column1='hello' where id=1

T2: SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED -- 事务隔离级别为允许脏读

go

select * from table where id=1

这里,T2的select可以查出结果。如果事务隔离级别不设为脏读,则T2会等T1事物执行完才能读出结果。

数据库如何自动加锁的?

1) T1执行,数据库自动加排他锁

2) T2执行,数据库发现事务隔离级别允许脏读,便不加共享锁。不加共享锁,则不会与已有的排他锁冲突,所以可以脏读。

例16:

----------------------------------------

T1: begin tran

update table set column1='hello' where id=1

T2: select * from table where id=1 --为指定隔离级别,则使用系统默认隔离级别,它不允许脏读

如果事务级别不设为脏读,则:

1) T1执行,数据库自动加排他锁

2) T2执行,数据库发现事务隔离级别不允许脏读,便准备为此次select过程加共享锁,但发现加不上,因为已经有排他锁了,所以就

等啊等。直到T1执行完,释放了排他锁,T2才加上了共享锁,然后开始读....

4 锁的粒度

锁的粒度就是指锁的生效范围,就是说是行锁,还是页锁,还是整表锁. 锁的粒度同样既可以由数据库自动管理,也可以通过手工指定hint来管理。

例17:

----------------------------------------

T1: select * from table (paglock)

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

T1执行时,会先对第一页加锁,读完第一页后,释放锁,再对第二页加锁,依此类推。假设前10行记录恰好是一页(当然,一般不可能

一页只有10行记录),那么T1执行到第一页查询时,并不会阻塞T2的更新。

例18:

----------------------------------------

T1: select * from table (rowlock)

T2: update table set column1='hello' where id=10

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

T2就可以顺利执行update操作。

例19:

----------------------------------------

T1: select * from table (tablock)

T2: update table set column1='hello' where id = 10

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

以上3例是手工指定锁的粒度,也可以通过设定事务隔离级别,让数据库自动设置锁的粒度。不同的事务隔离级别,数据库会有不同的

加锁策略(比如加什么类型的锁,加什么粒度的锁)。具体请查联机手册。

5 锁与事务隔离级别的优先级

手工指定的锁优先,

例20:

----------------------------------------

T1: GO

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE

GO

BEGIN TRANSACTION

SELECT * FROM table (NOLOCK)

GO

T2: update table set column1='hello' where id=10

T1是事物隔离级别为最高级,串行锁,数据库系统本应对后面的select语句自动加表级锁,但因为手工指定了NOLOCK,所以该select

语句不会加任何锁,所以T2也就不会有任何阻塞。

6 数据库的其它重要Hint以及它们的区别

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

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

与holdlock区别,见下例:

例21

----------------------------------------

T1:

begin tran

select * from table (tablock)

T2:

begin tran

update table set column1='hello' where id = 10

T1执行完select,就会释放共享锁,然后T2就可以执行update. 此之谓tablock. 下面我们看holdlock

例22

----------------------------------------

T1:

begin tran

select * from table (holdlock)

T2:

begin tran

update table set column1='hello' where id = 10

T1执行完select,共享锁仍然不会释放,仍然会被hold(持有),T2也因此必须等待而不能update. 当T1最后执行了commit或

rollback说明这一个事务结束了,T2才取得执行权。

3) TABLOCKX 对表加排他锁

例23:

----------------------------------------

T1: select * from table(tablockx) (强行加排他锁)

其它session就无法对这个表进行读和更新了,除非T1执行完了,就会自动释放排他锁。

例24:

----------------------------------------

T1: begin tran

select * from table(tablockx)

这次,单单select执行完还不行,必须整个事务完成(执行了commit或rollback后)才会释放排他锁。

4) xlock 加排他锁

那它跟tablockx有何区别呢?

它可以这样用,

例25:

----------------------------------------

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

而TABLELOCX不能这么用。

xlock还可这么用:select * from table(xlock tablock) 效果等同于select * from table(tablockx)

7 锁的超时等待

例26

SET LOCK_TIMEOUT 4000 用来设置锁等待时间,单位是毫秒,4000意味着等待

4秒可以用select @@LOCK_TIMEOUT查看当前session的锁超时设置。-1 意味着

永远等待。

T1: begin tran

udpate table set column1='hello' where id = 10

T2: set lock_timeout 4000

select * from table wehre id = 10

T2执行时,会等待T1释放排他锁,等了4秒钟,如果T1还没有释放排他锁,T2就会抛出异常: Lock request time out period exceeded.

8 附:各种锁的兼容关系表

| Requested mode | IS | S | U | IX | SIX | X |

| Intent shared (IS) | Yes | Yes | Yes | Yes | Yes | No |

| Shared (S) | Yes | Yes | Yes | No | No | No |

| Update (U) | Yes | Yes | No | No | No | No |

| Intent exclusive (IX) | Yes | No | No | Yes | No | No |

| Shared with intent exclusive (SIX) | Yes | No | No | No | No | No |

| Exclusive (X) | No | No | No | No | No | No |

9 如何提高并发效率

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

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

对记录加版本号.

对记录加时间戳.

对将要更新的数据进行提前读取、事后对比。

不论是数据库系统本身的锁机制,还是乐观锁这种业务数据级别上的锁机制,本质上都是对状态位的读、写、判断。

转载:数据库锁 - 口渴的火麒麟 - 博客园

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/61680.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Golang数据结构和算法

Golang数据结构和算法 数据的逻辑结构和物理结构常见数据结构及其特点算法的时间复杂度和空间复杂度Golang冒泡排序Golang选择排序Golang插入排序Golang快速排序Golang归并排序Golang二分查找Golang sort包Golang链表Golang container/list标准库Golang栈stackGolang二叉搜索树…

解决css样式中last-child不生效的问题

需求 项目中需要使用v-for指令来渲染一个图片列表&#xff0c; 现状 发现&#xff0c;最后一个格子并没有跟下面绿色线对齐。 最后发现 是因为 每个格子都给了 margin-right&#xff1a;36px&#xff0c;影响到了最后一个格子 所以使用last-child 将最后一个格子的margin 属性…

ARM DIY(六)音频调试

前言 今天&#xff0c;调试一下音频 硬件焊接 硬件部分核心是 LM4871 音频功放芯片 对于 SOC 来讲很简单&#xff0c;就一个引脚 HPOUTL&#xff08;单声道&#xff09;&#xff1b;对于扬声器来讲也很简单&#xff0c;就两个引脚&#xff0c;插上就可以了。 另外一个关键点…

FastDFS+Nginx - 本地搭建文件服务器同时实现在外远程访问「端口映射」

文章目录 前言1. 本地搭建FastDFS文件系统1.1 环境安装1.2 安装libfastcommon1.3 安装FastDFS1.4 配置Tracker1.5 配置Storage1.6 测试上传下载1.7 与Nginx整合1.8 安装Nginx1.9 配置Nginx 2. 局域网测试访问FastDFS3. 安装cpolar内网穿透4. 配置公网访问地址5. 固定公网地址5.…

vue3中如何使用el-tooltip中的插槽达到换行效果

el-tooltip的content属性中的内容可以使用插槽来替换 话不多说&#xff0c;直接上代码 <el-tooltip effect"light" placement"top-start"><div slot"content" class"tips"> // 在这里运用插槽<p class"tip-tex…

数据结构--5.1图的存储结构(十字链表、邻接多重表、边集数组)

目录 一、十字链表&#xff08;Orthogonal List&#xff09; 二、邻接多重表 三、边集数组 四、深度优先遍历 一、十字链表&#xff08;Orthogonal List&#xff09; 重新定义顶点表结点结构&#xff1a; datafirstInfirstOut 重新定义边表结构结点&#xff1a; tailV…

js 将形如 ‘Aug 30 2022‘ 格式的日期转化为 ‘%Y-%m-%d‘ 的格式。

可以使用JavaScript中的Date对象和相关方法来完成此任务。具体实现如下&#xff1a; // 定义需要转换的日期字符串 const dateStr Aug 30 2022;// 将日期字符串转换为Date对象 const date new Date(dateStr);// 使用Date对象的getDate、getMonth、getFullYear方法获取年月日…

OS 死锁处理

如果P先申请mutex 则mutex从1置零&#xff0c;假设申请到的empty 0则empty变成-1阻塞态 同理C中mutex从0变为-1&#xff0c;那么如果想离开阻塞态&#xff0c;那么就需要执行V&#xff08;empty&#xff09;但是如果执行V&#xff08;empty&#xff09;就需要P&#xff08;mu…

7.react useReducer使用与常见问题

useReducer函数 1. useState的替代方案.接收一个(state, action)>newState的reducer, 并返回当前的state以及与其配套的dispatch方法2. 在某些场景下,useReducer会比useState更加适用,例如state逻辑较为复杂, 且**包含多个子值**,或者下一个state依赖于之前的state等清楚us…

后端给前端传参数忽略空属性

JsonInclude JsonInclude注解用于指定在对象序列化为JSON字符串时&#xff0c;哪些属性应该被包含进去&#xff0c;哪些属性应该被忽略掉。 JsonInclude注解有以下几个常用选项&#xff1a; JsonInclude(JsonInclude.Include.NON_NULL)&#xff1a;表示只有属性值不为null的属…

stm32之25.FLASH闪存

打开标准库 源码--- int main(void) {uint32_t d;Led_init();key_init();/* 初始化串口1波特率为115200bps&#xff0c;若发送/接收数据有乱码&#xff0c;请检查PLL */usart1_init(115200);printf("this is flash test\r\n");/* 解锁FLASH&#xff08;闪存&#xf…

GPT 系列笔记

open ai 出品, 与 google 的 bert 系列 是不同的任务, NLGeneration vs. NLUnderstanding. 二. GPT-2 hugging-face 的 transformers 库中有模型源码, 为 from transformers.models.gpt2 import GPT2Model. 成员字段 wte, Word Token Embwpe, Word Position Embh, 模型主体…

运维数据(2):全新解说运维的价值和场景

在一个项目中&#xff0c;得知客户高层一直在纠结运维的价值&#xff0c;质疑运维投入的合理性和必要性&#xff0c;要求澄清其具体价值及其与业务的关系。同时&#xff0c;当今IT界流行“场景”一词&#xff0c;不论是解决方案&#xff0c;还是具体产品都在拉关系&#xff0c;…

亚马逊云科技 re:Inforce 大会云安全合规与技术实践及 Security Jam 大赛,快来报名吧!...

‍‍ 2023年8月31日在北京 亚马逊云科技 re:Inforce 大会 首次登陆中国&#xff01; 我们期待您的莅临&#xff0c; 并与您一起迎接 AI 时代&#xff0c; 开启全面智能的安全旅程&#xff01; 在13:00-17:00的 培训与动手实验环节中 云安全合规与技术实践 及 Security Jam 大赛…

Ansible学习笔记14

实现多台的分离实现&#xff1a; [rootlocalhost playbook]# cat example3.yaml --- - hosts: 192.168.17.105remote_user: roottasks:- name: create test1 directoryfile: path/test1/ statedirectory- hosts: 192.168.17.106remote_user: roottasks:- name: create test2 d…

Jenkins测试报告样式优化

方式一&#xff1a;修改Content Security Policy&#xff08;临时解决&#xff0c;Jenkins重启后失效) 1、jenkins首页—>ManageJenkins—>Tools and Actions标题下—>Script Console 2、粘贴脚本输入框中&#xff1a;System.setProperty("hudson.model.Directo…

Ubuntu学习---跟着绍发学linux课程记录(第一部分)

文章目录 1、启动、关闭、挂起、恢复&#xff08;电源&#xff09;2、更多虚拟机操作2.1 电源设置2.2 硬件参数设置2.3 状态栏2.4 全屏显示 3、快照与系统恢复4、桌面环境5、文件系统6、用户目录7、创建目录和文件8、命令行&#xff1a;文件列表ls 9、命令行&#xff1a;切换目…

3.使用IDE的优点

IDE是集成开发环境&#xff1a;Integrated Development Environment的缩写。 1、优点 使用IDE的好处在于&#xff0c;可以把编写代码、组织项目、编译、运行、调试等放到一个环境中运行&#xff0c;能极大地提高开发效率。 IDE提升开发效率主要靠以下几点&#xff1a; 编辑器…

Docker容器:docker consul的注册与发现及consul-template

Docker容器&#xff1a;docker consul的注册与发现及consul-template守护进程 一.docker consul的注册与发现介绍 1.什么是服务注册与发现 &#xff08;1&#xff09;服务注册与发现是微服务架构中不可或缺的重要组件。 &#xff08;2&#xff09;为解决服务都是单节点的&a…

XSS结合CSRF

假设我们获得了目标CMS的源码&#xff0c;搭建了一个相同的网站&#xff0c;我们在自己的网站执行添加用户的操作&#xff0c;并且用bp抓包 如图&#xff0c;这是我们抓到的添加用户的数据包 接下来&#xff0c;我们可以根据数据包构造js代码 <script> xmlhttp new XML…