1. 锁的概念
1.1. gbase8s的常规锁
gbase8s数据库的锁分为两种:共享锁和排他锁
- 共享锁:顾名思义,共享锁就是可以同时由多个用户同时获取到的锁资源
-
- 一个数据(行、页、表)被加上共享锁,则同时也可以被其他用户或者session添加共享锁,但是数据加上共享锁后,不能被更新
- 锁的添加不是单独添加的,一个共享锁的添加会同时引起其他共享锁的添加(意向锁会有解释)
- 由字母S表示
- 独占锁:只能同时由一个用户占用的锁资源,会阻止其他用户用任何方式访问资源
-
- 用户只有获取到了独占锁,才能对数据发起更新
- 数据上没有其他用户的锁的情况下,才能被添加独占锁
- 由字母X表示
1.2. 意向锁
意向锁:是为了保证数据的一致性而出现的一个锁,和行锁、页锁相关,当加行锁和页锁时,表上会出现意向锁
- 场景:当我们对一个表上的某行或者某页进行添加锁时,为了防止有人删除表或者库,会同时在表上和库上添加一个锁,从而防止其他用户在表或者库上添加独占锁,从而删除数据库或者表,造成数据的不一致,而这种锁被称之为意向锁
- 意向共享锁情景:
-
- 当我们对多张表进行查询时,首先会多个表上添加意向共享锁,如果表上有排他锁,则会加锁失败
- 当需要执行长时间的查询操作时,使用表上的意向共享锁,防止其他事务对表的系统数据进行修改,保证查询的正确性
- 意向排他锁:
-
- 当对表的个别数据进行加上排他锁时,会在整张表上添加意向排他锁,防止其他用户对整张表进行操作
1.3. 意向锁和常规锁的关系
- 当在表的行或者页上加共享行锁或者共享页锁时,会在表上加意向共享锁
- 当一个线索在表的页或者行上加排他行或者排他页锁时,会在表上加意向排他锁
- 当一个线索在表的页或者行上同时存在共享和排他锁时,会在表上加意向排他锁
除此之外会在库上加一些相关的锁
1.4. 常规锁和意向锁的排斥关系
常规锁 | S | X |
S | 不互斥 | 互斥 |
X | 互斥 | 互斥 |
- IX:意向排他锁
- IS:意向共享锁
意向锁 | IS | IX |
IS | 不互斥 | 不互斥 |
IX | 不互斥 | 不互斥 |
常规锁和意向锁 | IS | IX |
行级S | 不互斥 | 不互斥 |
表级S | 不互斥 | 互斥 |
X | 互斥 | 互斥 |
1.5. 锁的对象层次和类型
层次
- 数据库锁:当对数据库进行连接时,系统会在该数据库增加S锁或者X锁,防止其他用户对该数据库进行删除
- 表级锁:加锁对象是一个表,rowid为0,keynum为0
- 页级锁:gbase8s将多行记录放在数据页上,当采用页级锁进行访问时,gbase8s会自动对访问的数据页进行加锁,当按照物理顺序访问和更新多条记录时,页级锁的效率更高。
-
- 页锁显示为rowid以00结尾,即页中的所有行均锁定
- 行级锁:只需要锁定所访问的少数记录情况,使用行级锁的效率较高,行锁显示实际rowid,不以00结尾
- 字节锁:包含varchar数据类型上的行锁
- 键:键锁显示未keynum,如果行的索引需要更新,则锁在该行的索引
类型
- B:byte lock字节锁
- IS:intent shared lock意向共享锁
- S:shared lock共享锁
- XS:repeatable read shared key可重复度共享锁
- U:update lock更新锁
- IX:intent exclusive lock意向独占锁
- SIX:共享意向独占锁
- X:exclusive lock独占锁
- XR:repreatable read exclusive可重复读独占锁
1.6. 锁特殊情况死锁
即两个线索互持有了各自持有的资源,就会发生死锁
- 场景:两个事务,事务A和事务B
-
- 事务A:获取到了A表的排他锁,对A做了更新,没有提交
- 事务B:获取到了B表的排他锁,对B做了更新,没有提交
- 事务A:要对B表做更新操作,对B表做更新需要获取到B表的排他锁,但是B事务已经有排他锁,需要等待,等待B释放锁资源
- 事务B:要对A表做更新操作,对A表做更新需要获取到A表的排他锁,但是A事务已经有排他锁,需要等待,等待A释放锁资源
- 上述情形就会触发死锁
1.7. 锁的常规参数
- LOCKS:锁的数量,每个锁占用44个字节,锁使用完,服务器会自动申请16次锁,申请的数目是10万个和当前申请锁的最小值,额外申请的这些锁将占用虚拟段的内存
- DEF_TABLE_LOCKMODE:创建表默认的锁级别,建议为row行锁
- DEADLOCK_TIMEOUT:需要等待的锁的最大时间,死锁分为网络死锁和本地死锁
-
- 一个线索等待远程系统的响应,如果等待的时间已经超过了DEADLOCK_TIMEOUT的配置值,本地的服务器就认为是发生网络死锁,并返回一个错误到应用程序,当发生这种情况时,dlouts域的值就会增加
-
-
- 当网络较慢或者远程服务器的响应时间较慢时,可以尝试通过增加配置参数DEADLOCK_TIMEOUT的值来降低超时的发生,但是增加DEADLOCK_TIMEOUT的值意味着线索等待更长的时间才能看到响应
-
-
- 本地死锁:即两个线索互持有了各自持有的资源,就会发生死锁,发生死锁时,deadlks会加1
-
-
- deadlks增加异常后,需要检查应用使用的事务隔离级别,最后递交和脏读的事务隔离级别可以避免死锁的发生
-
1.8. 锁的生命周期
- 记日志是:执行了commit或者rollback work之后会释放
- 不记录日志:SQL语句执行结束之后,锁即释放
1.9. 当前数据库锁的查看
[gbasedbt@iZ2ze2nmdlhki0ezcrioayZ ~]$ onstat -k
Your evaluation license will expire on 2025-02-19 00:00:00
On-Line -- Up 1 days 00:03:23 -- 759248 Kbytes
--锁在共享内存中的地址(如果用户线程等待此锁,地址将会出现在onstat -u的wait字段中) 正在等待锁的用户线程 持有锁的共享内存地址,对应onstat -u的address 所有者持有的链接列表的下一个锁 锁的类型 锁定资源的tblspace编号,小于10000,表示enterprise replication伪锁 行标识号
Locks
address wtlist owner lklist type tblsnum rowid key#/bsiz
443795e0 0 473b8128 4506f528 HDR+IX 600460 0 0
44379668 0 473b5568 0 S 100002 209 0
4506f528 0 473b8128 0 HDR+S 100002 209 0
4506f5b0 0 473b8128 443795e0 HDR+X 600460 102 0 D
4506f638 0 473b8128 4506f5b0 HDR+B 600460 100 864
4506f6c0 0 473b8128 4506f638 HDR+X 600460 106 0 D
4506f748 0 473b8128 4506f6c0 HDR+X 600460 108 0 D
4506f7d0 0 473b8128 4506f748 HDR+X 600460 10a 0 D
4506f858 0 473b8128 4506f7d0 HDR+X 600460 10b 0 D
4506f8e0 0 473b8128 4506f858 HDR+X 600460 10d 0 D
- owner:正持有锁的线程的共享内存地址,此地址对应onsta -u输出的address字段中的地址。当owner值显示在括号中时,它代表事务结构的共享内存地址,只有锁是为全局事务而分配时,才会出现这种情况。该地址对应onstat -G的输出的地址字段
type标志
- HDR:头
- B:字节
- S:共享
- X:互斥
- I:意向
- U:更新
- IX:意向-互斥
- IS:意向-共享
- SIX:共享,意向-互斥
1.10. select、delete和insert的加锁情景
[gbasedbt@node02 ~]$ onstat -k
On-Line (Prim) -- Up 00:03:07 -- 1251008 Kbytes
Locks
address wtlist owner lklist type tblsnum rowid key#/bsiz
4439e248 0 47ee6ca8 0 S 100002 206 0
4439e2d0 0 47ee6ca8 4439e248 HDR+S 100002 201 0
4439e358 0 47ee9868 0 S 100002 206 0
4439e3e0 0 47eea128 0 S 100002 206 0
4439e9b8 0 47eeb2a8 0 S 100002 206 0
4439ea40 0 47eebb68 0 S 100002 206 0
4439f348 0 47eec428 0 S 100002 206 0
4439f788 0 47eecce8 0 S 100002 201 0
443a0668 0 47eea9e8 0 HDR+S 100002 206 0
--没有索引的情况下
[gbasedbt@node02 ~]$ onstat -k
On-Line (Prim) -- Up 00:03:57 -- 1251008 Kbytes
Locks
address wtlist owner lklist type tblsnum rowid key#/bsiz
4439e248 0 47ee6ca8 0 S 100002 206 0
4439e2d0 0 47ee6ca8 4439e248 HDR+S 100002 201 0
4439e358 0 47ee9868 0 S 100002 206 0
4439e3e0 0 47eea128 0 S 100002 206 0
--下面是加的INsert的X锁
4439e9b8 0 47eecce8 4439f348 HDR+X 100282 102 0 I
4439f348 0 47eecce8 4439f788 HDR+IX 100282 0 0
4439f788 0 47eecce8 0 S 100002 201 0
443a0668 0 47eea9e8 0 HDR+S 100002 206 0
--存在索引的情况下
[gbasedbt@node02 ~]$ onstat -k
On-Line (Prim) -- Up 00:07:15 -- 1251008 Kbytes
Locks
address wtlist owner lklist type tblsnum rowid key#/bsiz
4439e248 0 47ee6ca8 0 S 100002 206 0
4439e2d0 0 47ee6ca8 4439e248 HDR+S 100002 201 0
4439e358 0 47ee9868 0 S 100002 206 0
4439e3e0 0 47eea128 0 S 100002 206 0
4439ea40 0 47eecce8 4439f788 HDR+IX 100282 0 0
--有索引的情况下还会增加键值锁
4439ece8 0 47eecce8 4439ea40 HDR+X 100282 103 0 I
4439f348 0 47eecce8 4439ece8 HDR+X 100284 103 K- 1 I
4439f788 0 47eecce8 0 S 100002 201 0
443a0668 0 47eea9e8 0 HDR+S 100002 206 0
--没有索引的情况
[gbasedbt@node02 ~]$ onstat -k
On-Line (Prim) -- Up 00:05:21 -- 1251008 Kbytes
Locks
address wtlist owner lklist type tblsnum rowid key#/bsiz
4439e248 0 47ee6ca8 0 S 100002 206 0
4439e2d0 0 47ee6ca8 4439e248 HDR+S 100002 201 0
4439e358 0 47ee9868 0 S 100002 206 0
4439e3e0 0 47eea128 0 S 100002 206 0
4439e9b8 0 47eecce8 4439ea40 HDR+X 100282 102 0 I
--下一行是增加的delete锁,有D
4439ea40 0 47eecce8 4439f348 HDR+X 100282 101 0 D
4439f348 0 47eecce8 4439f788 HDR+IX 100282 0 0
4439f788 0 47eecce8 0 S 100002 201 0
443a0668 0 47eea9e8 0 HDR+S 100002 206 0
--存在索引的情况
[gbasedbt@node02 ~]$ onstat -k
On-Line (Prim) -- Up 00:08:18 -- 1251008 Kbytes
Locks
address wtlist owner lklist type tblsnum rowid key#/bsiz
4439e248 0 47ee6ca8 0 S 100002 206 0
4439e2d0 0 47ee6ca8 4439e248 HDR+S 100002 201 0
4439e358 0 47ee9868 0 S 100002 206 0
4439e3e0 0 47eea128 0 S 100002 206 0
4439e9b8 0 47eecce8 4439f348 HDR+X 100282 102 0 D
4439ea40 0 47eecce8 4439f788 HDR+IX 100282 0 0
4439ece8 0 47eecce8 4439ea40 HDR+X 100282 103 0 I
--关于delete的键值锁
4439ef08 0 47eecce8 4439e9b8 HDR+X 100284 102 K- 1 D
4439f348 0 47eecce8 4439ece8 HDR+X 100284 103 K- 1 I
4439f788 0 47eecce8 0 S 100002 201 0
443a0668 0 47eea9e8 0 HDR+S 100002 206 0
11 active, 300000 total, 65536 hash buckets, 0 lock table overflows
2. 锁在各个层次上的认识和区分方法
2.1. 库锁
有一个特殊的tblspace用来跟踪创建在服务器上的数据库,他是root dbspace的第二个tblspace,这个tblspace上的每一行包含了服务器上的每个数据库的信息。
- 当请求数据库级别的锁时,所发生的事情是会在数据库的tblspace上加一个行锁,如果数据库是被独占的锁住,则会在这个行上加一个独占锁
- 只要是非独占的任何数据库访问,都会在这个行上加一个共享锁,因此当一个数据库打开时会在这个行上加一个共享锁,多个用户同时访问一个数据库,则会在这个行上加多个共享锁。
- 服务器中没有办法把tblspace的rowid和数据库名称连接起来,但之间确实存在关系,第一个创建的rowid是最小的rowid,其他创建的数据库的rowid会增加,如果直到数据库创建的先后顺序,可以把rowid和数据库对应起来
[gbasedbt@VM-8-4-centos ~]$ onstat -k
Your evaluation license will expire on 2024-11-02 00:00:00
On-Line -- Up 9 days 19:00:05 -- 1145676 Kbytes
Locks
address wtlist owner lklist type tblsnum rowid key#/bsiz
4423f028 0 45fe54e8 442415e0 HDR+S 100002 207 0
442415e0 0 45fe54e8 0 HDR+S 100002 201 0
2 active, 100000 total, 32768 hash buckets, 0 lock table overflows
- 库锁的tblsnum是100002,代表是第二个tblspace
2.2. 表锁
表级锁会锁住整张表,表级锁可以是共享锁,允许其他的用户读表上的数据,或者是独占锁,不允许其他的用户访问表
- 如果一个用户在表上加了独占锁,无论它修改了多少记录,都不需要额外的锁;如果加的是共享锁,在对数据修改时,将需要更低级别的锁
- onstat -k的输出中,表级别的锁rowid列的值都是0,不和任何一个页面或记录有关,tblsnum列的值和表的part number是相同的,可以通过运行下面的SQL发现表和tblsnum的对应关系
-
- select * from systables where hex(partnum)='0x00200020';
2.3. 页级锁
创建表时可以指定行级锁或者是页级锁,如果是指定页级锁,无论页面上有多少条记录,锁会加在整个页面上,当锁住的记录占一个页面上的记录比例较高时,页级锁的效率会比较高
- 由于页级锁不会关系到slot number,因此rowid的最后两位总是00
2.4. 行级锁
- rowid由页面号和页面上的槽号组成,由于一个页面最多为255条数据,所以,slot number的范围是01~FF(十六进制),因此可以观察tblsnum的值(是所在的tblspace),且rowid的值不以00结尾,满足上述两种情况的为行级锁
2.5. 键值锁
- 当更新一张表的记录时,如果要更新一个或多个键值,在表上就会加一个或多个键值锁,健之锁加在索引页上的一个实体上
-
- 假设一张表有一个唯一性索引,当删除一条记录时,锁不仅会加在记录上,还会加在索引的键值上,这样可以保证在事务处理过程中索引的一致性
- 当使用了重复读的事务隔离级别,通过索引去访问记录行时,会在所有访问的键值上就爱共享锁,这是加共享锁的唯一条件。最通常的是插入、更新、删除时,在索引上加独占锁
- 键值锁:在onstat -k的输出中,key#/bsize列的值可以识别是否使用了键值锁
-
- 以字母K打头
- 修改了一个变长的域(数字是修改了多少字节)
2.6. 字节锁
当一张表使用了一个变长的列时(varchar),由于在更新期间记录的长度会变化,因此锁住记录的一部分是有必要的,在这种情况下,会使用字节锁(byte lock)
- 字节锁只锁住修改的那一部分,实际锁住的部分显示在onstat -k的bsiz列,rowid只用于表名这条记录所在的页面,因此后两位为00
- 识别字节锁:当onstat -k的type列有字母B,且bsiz列是一个大于0的值,表示为字节锁
- 如果修改的表设置锁级别为页级锁,此时会将页级锁和字节数进行合并,此时TYPE列是X,bsize是大于0的值、
2.7. 智能大对象的byte-range锁
- byte-range允许只锁住访问的那些字节,多个用户访问同一个大对象的不同byte-range,这种情况时允许的
- 锁内存使用率对byte-range锁的影响
-
- 当整个系统的锁的内存使用率小于50%时,才有可能授予byte lock
- 50%~70%的锁内存已经被使用时,首先会尝试锁住整个大对象,如果整个大对象不能被锁住,就不再申请byte range锁
- 如果超过70%,则不允许使用range lock,会自动升级到大对象的锁
- 这种升级机制是不能人工干预的,是数据库基于已经使用的锁资源的百分比而自动升级的
智能大对象创建时可以指定锁的模式
#以下方式将锁住整个大对象
onspace -c -S sbspace -p /path -o 0 -s 500000 -Df "LOCK_MODE=BLOB"
#以下方式将锁住访问的部分
onspace -c -S sbspace -p /path -o 0 -s 500000 -Df "LOCK_MODE=RANGE"
2.8. 监控range locks
当输出onstat -k时,有两种输出
- 如果没有大对象的byte-range锁,则onstat -k输出没有区别
- 如果存在了这种锁,则onstat -k上面会输出常规锁,下面会输出新的一段关于byte range locks的内容
-
- 对于使用了byte range锁的大对象,会打印出rowid和partition number,接着列出byte -range锁的列表以及持有范围
- 除本身锁之外还会列出byte range锁的事务以及所等待的ranges
3. 锁的监控和锁资源的判断
3.1. 锁资源的判断onstat -p
监控数据库资源是否不足,可以通过onstat -p监控,主要有三个域和资源
[gbasedbt@VM-8-4-centos ~]$ onstat -p
Your evaluation license will expire on 2024-11-02 00:00:00
On-Line -- Up 9 days 06:09:41 -- 1145676 Kbytes
Profile
dskreads pagreads bufreads %cached dskwrits pagwrits bufwrits %cached
4892 34155 1062400 99.54 29647 329293 329139 90.99
isamtot open start read write rewrite delete commit rollbk
1232511 75355 98656 169876 195284 5654 31885 11891 3
gp_read gp_write gp_rewrt gp_del gp_alloc gp_free gp_curs
0 0 0 0 0 0 0
--锁资源的超时
ovlock ovuserthread ovbuff usercpu syscpu numckpts flushes
0 0 0 150.87 109.56 903 1814
-- 锁资源
bufwaits lokwaits lockreqs deadlks dltouts ckpwaits compress seqscans
5 10 748008 0 0 6 3799 283
ixda-RA idx-RA da-RA logrec-RA RA-pgsused lchwaits
0 269 0 0 0 288
关于锁的含义
- ovlock:会话尝试超过锁最大数量的数量,如果大于0,说明发生过锁资源不足,需要增加locks参数
- lokwaits:发生锁等待的次数
- lockreqs:请求锁的数量
- deadlks:检查死锁并阻止的次数,大于10应该进行检查
- dltouts:等待锁超时的次数
锁资源的判断
- lokwaits和lockreqs:lockwaits和lockreqs列可以用来识别可能得锁竞争,lockwaits占lockreqs的百分比应该小于1%,如果等待的百分比很高,可以考虑调整下列区域
-
- 页级锁:太多的页级锁可能会导致锁等待,考虑使用行级锁
- 表级锁:使用表级锁的应用应考虑使用其他锁方式
- 应用的事务隔离级别:检查应用是否使用了重复读或者cursor stability的事务隔离级别,考虑是否可以使用最后递交或者脏读的事务隔离级别
3.2. 查看当前数据库产生阻塞情况下持有锁的页面ID和等待锁的页面ID
onstat -u | awk '{print $1, $3}' | awk 'NR==FNR{a[$1]=$2; next} NR!=FNR && $2 in a && $3 in a {printf "持有锁sid:%s 持有锁uaddr:%s 等待锁uaddr:%s 等待锁sid:%s\n", a[$2], $2, $1, a[$1]}' - <(onstat -k | awk '$2~ /[1-9]/ {print $2, $3}')
3.3. onstat -u和onstat -k对锁查看的联合使用(3.2的命令依据)
- onstat -u可以查看线索的状态,可以看出线索是否在等待锁、回滚事务或者等待checkpoint完成
-
- 一般wait是空值,如果非空,可以根据flags第一位来判断等待内容
- onstat -k显示的是一个锁的输出报告
-
- 其中wtlist中有45fe54e8是被锁的线索,其没有获取到锁资源,根据其与onstat -u对应的userthreads,发现sessid为47,根据onstat -k被锁的那一行中的owner,此显示为已经获取资源的用户,其对应onstat -u的第25行对应sessid为46,表明是sessid46阻塞了sessid 47
onstat -u
onstat -k
3.4. onstat -u打印用户线程
[gbasedbt@iZ2ze2nmdlhki0ezcrioayZ ~]$ onstat -u
Your evaluation license will expire on 2025-02-19 00:00:00
On-Line -- Up 09:51:42 -- 759248 Kbytes
Userthreads
--线程的共享内存地址 会话状态的标志 会话唯一标识 会话用户 终端类型 锁的地址 锁等待时间 用户线程持有的锁数 用户线程的磁盘读取数 用户线程读取的写调用数
address flags sessid user tty wait tout locks nreads nwrites
473a0028 ---P--D 1 gbasedbt - 0 0 0 106 1298
473a08e8 ---P--F 0 gbasedbt - 0 0 0 0 3585
473a11a8 ---P--F 0 gbasedbt - 0 0 0 0 9
flags标志位
- 位置1:
-
- B:正在等待缓冲区
- C:正在等待checkpoint
- G:正在等待对逻辑日志缓冲区的写入
- L:正在等待锁
- S:正在等待互斥体,针对共享资源,同时只向一个线程授予共享资源的独占访问权,如果一个线程已经获取了互斥体,获取该互斥体的第二个线程将会被挂起
- T:正在等待事务
- Y:正在等待条件
- X:正在等待事务清除(回滚)
- 位置2
-
- *:IO故障过程中的事务是活动的
- 位置3
-
- A:Dbspace备份线程
- B:begin work has been logged
- P:
- X:XA prepared
- C:正在递交事务
- R:正在回滚或已经回滚
- H:Heuristically aborting
- 位置4
-
- P:会话主线程
- 位置5
-
- R:正在读取
- X:临界段中的线程
- 位置6
-
- R:恢复过程中使用的线程
- -:恢复过程中未使用的线程
- 位置7
-
- B:B-tree清除程序线程
- C:已终止正在等待清除的用户线程
- D:守护程序线程
- F:页清除程序线程
- M:Sepcial monitor