达梦7死锁问题思考

现场跟踪程序的时候,在达梦客户端操作sql执行完没有提交导致应用程序中对应的更新语句死锁,因为是刚开发完的模块所创建的新表,所以影响不大。现将引起死锁的操作以及处理过程重现,希望看到的朋友以此为戒。
创建成绩表并录入记录,创建和录入语句如下所示:
CREATE TABLE "TEST"."SCORE_INFO"
(
"STUDENTID" NUMBER(22,0) NOT NULL,
"SUBJECT" VARCHAR2(16) NOT NULL,
"SCORE" NUMERIC(8,0),
NOT CLUSTER PRIMARY KEY("STUDENTID", "SUBJECT")) STORAGE(ON "OPEN_DATA", CLUSTERBTR);
COMMENT ON TABLE "TEST"."SCORE_INFO" IS '成绩表';
COMMENT ON COLUMN "TEST"."SCORE_INFO"."SCORE" IS '成绩';
COMMENT ON COLUMN "TEST"."SCORE_INFO"."STUDENTID" IS '学号';
COMMENT ON COLUMN "TEST"."SCORE_INFO"."SUBJECT" IS '学科名称';

insert into TEST.SCORE_INFO ("STUDENTID","SUBJECT","SCORE") select 2021001, '生物', 80;
insert into TEST.SCORE_INFO ("STUDENTID","SUBJECT","SCORE") select 2021001, '数学', 92;
insert into TEST.SCORE_INFO ("STUDENTID","SUBJECT","SCORE") select 2021001, '英语', 70;
insert into TEST.SCORE_INFO ("STUDENTID","SUBJECT","SCORE") select 2021001, '语文', 80;
insert into TEST.SCORE_INFO ("STUDENTID","SUBJECT","SCORE") select 2021002, '生物', 77;
insert into TEST.SCORE_INFO ("STUDENTID","SUBJECT","SCORE") select 2021002, '数学', 90;
insert into TEST.SCORE_INFO ("STUDENTID","SUBJECT","SCORE") select 2021002, '英语', 70;
insert into TEST.SCORE_INFO ("STUDENTID","SUBJECT","SCORE") select 2021002, '语文', 80;
设置达梦客户端,"窗口"->"选项",在选项弹出框中,选择"查询分析器", "自动提交"复选框不选中即可(现场为了方便回滚,就是处于不选中状态),如下图1所示。

图1
打开两个达梦客户端新建查询页,分别输入以下SQL:
标签一SQL:
update test.score_info
set score = '85'
where studentId = '2021001' and subject = '生物';

update test.score_info
set score = '87'
where studentId = '2021002' and subject = '生物';
标签二SQL:
update test.score_info
set score = '88'
where studentId = '2021002' and subject = '生物';

update test.score_info
set score = '86'
where studentId = '2021001' and subject = '生物';
当标签一中的SQL执行完,执行标签二的第一个SQL时,即会出现死锁,可通过以下SQL进行验证:
select lc.lmode,lc.trx_id,lc.blocked,vtw.wait_for_id as waitTrxId,vtw.wait_time,
       vs.sess_id,vs.sql_text,vs.appname,vs.clnt_ip 
from v$lock lc
inner join v$trxwait vtw on lc.trx_id = vtw.id 
inner join v$trx vt      on vtw.id = vt.id 
inner join v$sessions vs on vt.sess_id = vs.sess_id 
where vs.sql_text is not null;
其中v$lock视图是显示活动的事务锁信息动态视图,v$trxwait是显示事务等待信息动态视图,v$trx是显示所有活动事务信息的动态视图,v$sessions是显示会话具体信息的动态视图。
结果如下图2:

图2
处理方式为强制停止该会话,上图中的会话字段为sess_id,因此处理语句为:
sp_close_session(462499288);
commit;
执行完,再次使用上面的死锁查询语句,结果集为空,表明死锁已解除。
insert语句的逻辑与此类型,例如:标签一录入学号为2021004,学科为语文与2021005,学科为语文的记录;标签二录入学号为2021005,学科为语文与2021004,学科为语文的记录。具体操作此处不再赘述。
下面介绍下达梦的锁模式和锁粒度。
1) 达梦数据库有四种锁模式:共享锁、排他锁、意向共享锁、意向排他锁。
共享锁(Share Lock,S锁) : 用于读操作,防止其他事务修改正在访问的对象。这种封锁模式允许多个事务同时并发读取相同的资源,但是不允许任何事务修改这个资源。
排他锁(Exclusive Lock,X锁) : 用于写操作,以独占的方式访问对象,不允许任何其他事务访问被封锁的对象,防止多个事务同时修改相同的数据,以避免引发数据错误;防止访问一个正在被修改的对象,以避免引发数据不一致。一般在修改对象定义时使用。
意向锁(Intent Lock)用于读取或修改被访问对象数据时使用,多个事务可以同时对相同对象上意向锁,DM支持两种意向锁:
意向共享锁(Intent Share Lock,IS锁) : 一般在只读访问对象时使用。
意向排他锁(Intent Exclusive Lock,IX锁) : 一般在修改对象数据时使用。
四种锁模式的相容矩阵如下表所示,其中"Y"表示相容,其中"N"表示不相容。如表中第二行第二列为"Y",表示如果已经加了IS锁,其他用户还可以继续添加IS锁,第二行第五列为"N",表示如果已经加了IS锁,其他用户不能再添加X锁
                                       |      | IS   | IX   | S    | X    |
                                       | IS   | Y    | Y    | Y    | N    |
                                       | IX   | Y    | Y    | N    | N    |
                                       | S    | Y    | N    | Y    | N    |
                                       | X    | N    | N    | N    | N    |
层次越高的锁(例如:表锁),可以有效减少对资源的占用,减少锁检查的次数,但与此同时会严重影响并发。层次越低的锁(如下面要讲到的TID锁),有利于并发执行,但在事务请求对象比较多的情况下,需要大量的锁检查。意向锁的就是为了解决高层次锁限制并发的问题。
当表加了IX锁,表明表中有记录正在做修改操作:
a) 此时如果对表做DDL操作,需要请求表的X锁,那么在检测到表持有IX后就直接等待了,无需逐个检查表中的行是否持有行锁,有效减少了检查开销。
b) 此时如果有其他的读写事务需要操作,检测到表加的是IX而非X,只要读写事务没有涉及已经加了X锁的行即可正常执行,增大了系统的并发度。
2) 按照封锁对象的不同,可分为TID锁和对象锁两种。
TID锁以事务号为封锁对象,为每个活动事务生成一把TID锁,代替了其他数据库行锁的功能,以防止多个事务同时修改同一行记录。DM实现的是行级多版本,每一行记录银行一个TID字段,用于事务可见性判断。
执行INSERT、DELETE、UPDATE操作时,设置事务号到TID字段。这相当于隐式地对记录上了一把TID锁,INSERT、DELETE、UPDATE操作不再需要额外的行锁,避免了大量行锁对系统资源的浪费。只有多个事务同时修改同一行记录时,才会产生新的TID锁。例如,当事务T1(事务号为TID1)试图修改某行数据时,而该行数据正在被另一个事务T2(TID2)修改,此时事务T1会生成一个新的TID锁,其锁对象为事务号TID2,而非事务2。
上面一段话解释了图2中为什么会有三行记录:第一行通过意向排他锁(IX)记录锁住了test.score_info表,第二行是事务(2458385266)新生成的TID锁,其锁对象是事务(2458367479),第三行事务(2458385266)的自己的操作。
同时多版本写不阻塞读的特性,SELECT操作已经消除了行锁,因此DM中不再有行锁的概念。
对象锁是DM新引入的一种锁,通过统一的对象ID进行封锁,将对数据字典的封锁和表锁合并为对象锁,以达到减少封锁冲突、提升系统并发性能的目的。以下是通常数据字典和表锁各自应承担的功能:
数据字典:用来保护数据字典对象的并发访问,解决DDL并发和DDL/DML并发问题,防止多个事务同时修改同一个对象的字典定义,确保对同一个对象的DDL操作是串行执行的。并防止一个事务在修改字典定义的同时,另一个事务修改对应表的数据。
表锁:表锁用来保护表数据的完整性,防止多个事务同时采用批量方式插入、更新一张表,防止向正在使用FAST LOADER工具装载数据的表中插入数据等,保证这些优化后数据操作的正确性。此外,表锁还有一个作用,避免对存在未提交修改的表执行ALTER TABLE、TRUNCATE TABLE操作。
为了实现与数据字段锁和表锁相同的封锁效果,从逻辑上将对象锁的封锁动作分为四类:
a) 独占访问(EXCLUSIVE ACCESS),不允许其他事务修改对象,不允许其他事务访问对象,使用X方式封锁。
b) 独占修改(EXCLUSIVE MODIFY),不允许其他事务修改对象,允许其他事务访问对象,使用S+IX方式封锁。
c) 共享修改(SHARE MODIFY),允许其他事务修改对象,允许其他事务访问对象,使用IX方式封锁。
d) 共享访问(SHARE ACCESS),允许其他事务修改对象,允许其他事务访问对象,使用IS方式封锁。
最后再回到图2,图中有三行内容,第一行是对test.score_info表加上了意向排他锁(IX),即共享修改,第二行第三行是针对特定的行加了TID锁。这样的设计使得即使发生了上述的死锁,也不影响其他事务查询、修改或删除该表的其他记录,亦不影响其他事务查询该表的该条死锁记录。
posted on 2021-09-13 22:42  IT-飞鹰  阅读(284)  评论(0编辑  收藏  举报