爱收集资源网

我的账户余额增加1个亿,你的锁也一样

网络整理 2022-04-20 00:04

锁,很好理解,每个人都在自己的房子上安装锁,你拥有锁,只有你可以拥有房子,其他人无法进入。数据库中的锁也是一样的,但更细分。

db2 中有两种基本类型的锁:

db2 事务的隔离

这种锁机制用于事务隔离。这里我们不得不说一下什么是事务。事务是由数据库管理系统执行的一组数据库操作。它是一个逻辑单元。一个事务是为了保证一组数据库操作。要么全部成功,要么全部失败。可以简单理解为:事务是一组SQL语句,以begin(开始)事务开始,以commit或rollback结束。Commit表示提交,将事务中对数据库的所有更新写入到磁盘上的物理数据库中,事务正常结束。rollback 表示回滚,即如果事务运行过程中发生某种故障,事务就无法继续,

那么为什么需要事务隔离呢?这个问题可以反过来理解。如果不隔离,会出现以下三种现象:

1、脏读。即事务读取其他事务的未提交数据。上一篇《SQL语句提交后,db2做了什么?》提到数据库的增删改查都是在缓冲区中进行的,查询操作也优先在缓冲区中查找。如果未提交数据被读取,数据可能不正确,因为未提交的数据可能随时回滚,一旦回滚,读取的数据肯定是无效的。

比如:你给我转账1亿,然后我查询我的账户余额,有两笔交易: 交易B:我的账户余额增加1亿,你的账户余额减少1亿。交易A:读取我的账户余额1亿。时间点如下:

时间点事务 A 事务 B

一个开始

-

1

查询账户余额为0

B 开始

2

-

转入A一亿,A账户增加一亿

3

查询账户余额为1亿

B账户余额减少1亿

4

-

B 提交

在时间点 3,交易 B 尚未提及,交易 A 已读取 1 亿。这个时候不考虑我已经有1亿了,因为事务B可能会失败回滚。

2、幻读。就是读取其他事务提交的数据,但是第一次没有读取的数据,第二次读取。也可以这样理解,一个事务的第一个查询的结果集被其他事务插入到一个新的行中并提交给数据库,导致第二个事务的第一个查询中没有出现的结果集询问。在某些情况下,这是合理的,举个例子理解如下:

时间点事务 A 事务 B

一个开始

-

1

查询账户余额为1亿

B 开始

2

提现1亿

转入A亿并提交

3

查询账户余额还是1亿

-

第一次查询我的账户余额是1亿,但是我拿走后第二次查询还是1亿,好像有幻觉,所以叫幻读,因为第二次读的是提交的数据事务 B 并提交。数据不会修改第一个查询结果,也不会插入新数据。在这种情况下是合理的。

3、不可重复读取。类似于幻读,它读取其他事务提交的数据。事务中第一次读取的数据不能被第二次读取。也可以这样理解。一个事务的第一次查询结果集被其他事务更新并提交到数据库,导致第二次查询不可用,所以称为不可重复读,会因变化而导致原来的决策出现偏差在条件下,但有时这种情况也是合理的,例子如下:

时间点事务 A 事务 B

一个开始

-

1

查询账户余额为1亿

B 开始

2

准备退出

老婆从A账户转账1亿,提交

3

重新查询账户余额为0

-

由于实际应用场景非常复杂,而且不同业务所需的隔离级别不同,所以在数据库开发时必须考虑事务的隔离级别,否则会出大问题。

db2有相应的隔离级别来避开以上三个站点(其他数据库也有相应的级别,名称可能不同,请注意对比),如下图:

(是表示允许,否表示不允许)

隔离级别 脏读 不可重复读 幻读

未提交的读取

是的

是的

是的

光标稳定性

是的

读取稳定性

是的

可重复读取

表中的内容可能记不住,但只要理解了,就会很容易记住。如果还是不明白,请看这4个隔离级别的说明:

1、未提交读

db2中的“select * from xxx with ur”中的with ur是什么意思,现在你可能明白了表行锁定的时候能查询么,ur是Uncommitted Read,即未提交读的隔离级别,允许脏读,不加行锁。选择时,无需等待更新数据。

你可以在shell中测试它

外壳窗口 1

#+c 表示不自动提交
db2 +c "insert into sometable values('value1')"

外壳窗口 2

##脏读
select * from sometable where col = 'value1' with ur"

外壳窗口 3

##游标稳定性 with cs
select * from sometable where col = 'value1' "

您可以在窗口 2 中看到结果,在窗口 3 中没有结果。

2、光标稳定性

db2 "select * from xxx with cs",这里with cs也可以省略,因为默认的隔离级别是this,在这个隔离级别下,在一个事务中,结果集中只有被读取的行(指向的行通过游标)将被添加一个 NS 锁(什么是 NS 锁,如下所述),其他未处理的行将不会被锁定。此隔离级别仅保证正在处理的行的值不会被其他并发程序更改。

3、读取稳定性

如果使用这种隔离级别,则事务中所有读取的行都会加 NS 锁,直到事务提交或回滚后才会释放行上的锁。这确保了即使在一个事务中多次读取同一行,结果值也不会改变。但是,如果使用此隔离级别,则在事务中,如果使用相同的搜索条件重新打开已处理的游标,则结果集可能会更改。(可能会添加一些线,这些线称为幻线(Phantom)),对应幻读。这是因为 RS 隔离级别不会阻止插入或更新操作将新行添加到结果集中。

4、可重复读取

这是最严格的隔离级别。如果使用这个隔离级别,事务中所有读取的行都会加S锁,行上的锁会被释放,直到事务提交或回滚。. 这确保了即使在一个事务中多次读取同一行,结果值也不会改变。另外,如果一个已经处理过的游标在同一个事务中用相同的搜索条件重新打开,结果集不会改变。重复读取比读取稳定性具有更广泛的锁定范围。

为了读取的可靠性,应用程序只锁定所有符合要求的行,而对于重复读取,应用程序锁定所有扫描的行。例如,如果应用程序扫描表中的 10,000 行数据并最终找到与搜索条件匹配的 100 行。如果应用程序使用读可靠性隔离级别,应用程序将只锁定 100 个符合条件的行;如果应用程序使用重复读隔离级别,应用程序将锁定所有已扫描的 10,000 行。锁。

更多关于 db2 锁的实战练习,请参阅了解 DB2 通用数据库中的锁

db2 锁

DB2 支持锁定表空间、表、行和索引(大型机支持数据页)。通常会考虑表锁和行锁。

锁定策略:

添加表锁:表中的所有行都受到相同程度的影响。

加行锁:如果锁的范围是表的行及其下属,表加锁后,必须加锁对应的数据行。

表锁如下表所示:

名称 缩写 全名 描述

无意图锁(Intent Node),无需行锁

所有者可以读取所有数据,包括其他事务的未提交数据,但不能对表中的数据进行更改

Intent Share,需要行锁配合

所有者可以读取该行的数据,但不能在持有相应行的 S 锁时修改数据

意向排他锁(Intent eXclusive),需要行锁配合

拥有者可以在持有对应行的X锁的同时修改该行的数据

共享和意向排他锁(Share with Intent eXclusive),需要行锁配合

所有者可以读取表中的任何数据,如果可以在相应行上获得 X 锁,则可以修改该行。收购 SIX 很特别。它是在程序拥有IX锁时请求S锁,或者已经拥有S锁时请求IX锁时产生的。

小号

共享锁(Share),不需要行锁配合

可以读取表中的任何数据。如果给表加了S锁,表上的数据只能读取,不能修改。

ü

更新锁(Update),不需要行锁配合

所有者可以读取表中的任何数据,如果升级为X锁,则可以更改表中的任何数据,这是等待数据修改的中间状态

X

排他锁(eXclusive),不需要行锁配合

所有者可以读取或修改表中的任何数据。如果加了X锁,除了未提交的读事务外,其他程序不能读取或修改表。

Z

超级排他锁(Super eXclusive),不需要行锁配合

这个锁一般不是DML产生的,而是Drop、Alter或者创建删除索引的时候产生的。添加 Z 锁后,所有程序(包括未提交的读取器)都无法读取或修改表。

对 db2 意图锁的理解

牛老师注:对于IN、IX、IS、SIX意图锁,我们可以这样理解:严格来说,它们不是一种锁,而是将行锁信息存储在一张表中。举个简单的例子,让我们去一家酒店。整个酒店可以比作一张桌子,每个房间都是一排。我们在订房的时候,给行(房)加了一个X锁,但同时我们会为前面的行(房)做一个信息登记(旅客姓名,逗留时间等)酒店的办公桌。你可以把意向锁想象成这家酒店的前台。它不是真正意义上的锁。它维护着表中每一行的锁定信息,并且是共享的。后续旅客将通过酒店前台查询有空房间。那么,如果没有锁定的意图,会发生什么?假设我想呆在一个房间里,那么我每次都要去每个房间看看这个房间是否有人。,显然这样做的效率很低。事实上,最早的 DB2 版本是没有意图锁的,但这对并发性影响很大,后来才加了意图锁。所有数据库(Oracle、Infomix 和 Sybase)都有意图锁定实现。

DB2 支持的行锁如下:

名称缩写全名表锁所需的最低级别描述

小号

共享锁(共享)

正在读取该行,其他程序只能执行读取操作

ü

更改锁定(更新)

一个程序正在读取并可能修改该行,其他程序只能读取该行

X

排他锁(eXclusive)

该行正在被一个程序修改,其他程序无法访问该行

W

弱排他锁(Weak eXclusive)

将一行插入到表中后,将在该行中添加一个 W 锁。只有锁的所有者才能修改行。与 X 锁的不同之处在于该锁与 NW 锁兼容。

NS

下一个共享锁定(下一个共享)

所有者和其他程序可以读取该行,但不能修改它。当程序处于 RS 或 CS 隔离级别下时,锁可以代替 S 锁

NX

下一键排他锁(NexteXclusive)

当一行数据插入索引或从索引中删除时,会在该行的下一行添加一个NX锁,锁的所有者可以读取该行的数据但不能修改。此锁类似于 X 锁,但兼容 NS 锁

西北

Next弱独家

当某行的数据插入索引时,该行的下一行会被加 NW 锁,锁的拥有者可以读取但不能修改该行的数据,类似于 X 锁和 NX 锁,但是与W锁和NS锁兼容

db2 锁翻译

当程序向数据库请求其已锁定的对象上的锁时,数据库会将对象上的当前锁与请求的锁模式进行比较,如果请求的锁级别较高,则将当前锁升级为请求的锁。锁。锁。

锁级比较:

作为一种特殊情况,如果持有 S 锁请求 IX 锁,或者持有 IX 锁请求 S 锁,则锁转换的结果是 SIX 锁。

db2 锁升级

DB2 中有两个参数,LOCKLIST 和 MAXLOCKS:

当超过这个百分比时,会进行锁升级,所以增大这两个参数的值可以有效避免锁升级,但是会占用更多的内部空间,比如

$ db2 get db cfg|grep -i lock
 Max storage for lock list (4KB)              (LOCKLIST) = 200000
 Percent. of lock lists per application       (MAXLOCKS) = 60
 Interval for checking deadlock (ms)         (DLCHKTIME) = 10000
 Lock timeout (sec)                        (LOCKTIMEOUT) = 120
 Block log on disk full                (BLK_LOG_DSK_FUL) = NO
 Block non logged operations            (BLOCKNONLOGGED) = NO

这里LOCKLIST占用的内存大小为200000*4/1024=781.25 MB

单个应用锁最多可占用 468.75MB 内存。当一个程序加锁的行太多时,DB2会将多行的加锁改为加锁整个表并升级为表锁,从而减少内存使用。

DLCHKTIME默认为10000ms或10s,即检查死锁的频率表行锁定的时候能查询么,即每10秒检查一次是否发生死锁。如果存在列锁,数据库将中止死锁的应用程序(通常是工作最少的应用程序),这将释放该应用程序持有的所有锁并允许其他应用程序继续工作,DB2 将向已终止应用程序的 SQLCA。

LOCKTIMEOUT,可以设置这个参数的值来设置遇到锁阻塞后的等待时间。如果超过这个时间,数据库会自动回滚事务。

(结束)

公众号 somenzz 坚持原创,与你一起学习技术。

db2 事务隔离级别 隔离级别