技术开发 频道

专家点评:达梦6.0试用之并发和锁机制

  【IT168 独家

  编者言:在4月份举办的2010中国数据库与商业智能技术大会上,国产数据库的代表厂商达梦高调亮相,给一向由国外厂商把持的企业商业数据库市场带来不小的波澜。人们开始纷纷将目光投向国产数据库软件市场,开始关注由中国人自己开发的数据库产品。

  国产数据库产品究竟能不能用?在功能和特性上够不够用?为了解开这些疑问,我们特邀请在数据库业界众多知名的技术专家亲身试用相关产品,并真实地写出自己的试用感受。这些专家大都是Oracle数据库的ACE,或者是对其它主流的商业数据库十分精通和了解,让他们站在这样的角度来客观写出自己对国产数据库的技术评测,是非常有说服力的。

  尽管各位专家的工作都十分繁忙,但他们还是都认为,将国产数据库软件在技术上的真实情况书写出來,给出自己中肯的评价,这是一件非常有意义的工作。 更令人欣慰的是,相比过去,今天像达梦这样的国产数据库产品已经显得非常自信,它们希望听到业界技术专家的建议甚至是批评,不断完善自己的产品和功能,这将是一个非常良性的循环,长此下去,我们相信,摆在国产数据库脚下的将是更为宽广的道路!

  作者简介:杨廷琨(网名Yangtingkun),现任海虹医药电子商务有限公司首席DBA, ITPUB论坛Oracle数据库管理版版主,知名社区技术专家。2004年曾参与编写《Oracle数据库性能优化》一书,2007年被Oracle公司授予Oracle ACE称号,喜欢研究Oracle相关的技术问题,他的技术博客上积累了1500多篇Oracle相关的原创技术文章。

  前几天ITPUB的熊主编和我联系,希望我能参加国产数据库达梦的试用活动,并写几篇使用感受。本来最近手头的事情比较多,本打算推辞的,不过熊主编再三邀请,而且强调并非是枪手文,只要写出真实使用感受即可。既然如此,我就本着支持国产数据库的原则,写几篇试用感受。

  达梦6.0 for Windows安装

  达梦6.0试用之测试环境搭建

  达梦6.0试用之SQL篇

  达梦6.0试用之数据类型

  达梦6.0试用之PLSQL篇

  达梦6.0试用之数据库对象

  达梦6.0试用之架构篇

  达梦6.0试用之数据字典

  达梦6.0试用之分区

  达梦6.0试用之并发和锁机制

  达梦6.0试用之备份恢复(即将发布)

  达梦6.0试用之导入导出(即将发布)

  由于本人唯一熟悉的数据库就是Oracle,因此所有的对比都是与Oracle数据库进行对比,在这个过程中,将尽可能避免将对Oracle数据库的喜爱之情带进来,争取站在一个比较公正的位置上来进行评价。

  这一篇简单介绍一下达梦数据库的锁机制和并发控制。

  达梦的锁机制和Oracle的并不相同,Oracle和其他数据库相比最突出的特点就是锁的实现,一方面是行级锁,而且锁并非资源,只是占用极少的存储空间;另一方面Oracle没有读锁,读不阻塞写,写也不会阻塞读。即使DB2、SQLSERVER等数据库也无法做到这一点,显然不能要求达梦达到这样的标准。

  看看达梦的锁实现:

SQL>CREATE TABLE T
2   (ID NUMBER,
3   NAME VARCHAR(30),
4   AGE NUMBER);
CREATE TABLE T
(ID
NUMBER,
NAME
VARCHAR(30),
AGE
NUMBER);
time used:
45.975(ms) clock tick:76680990.
SQL
>INSERT INTO T
2   VALUES (1, 'A', 10);
INSERT INTO T
VALUES (1, 'A', 10)
1 rows affected
time used:
0.473(ms) clock tick:770450.
SQL
>INSERT INTO T
2   VALUES (2, 'B', 20);
INSERT INTO T
VALUES (2, 'B', 20)
1 rows affected
time used:
0.423(ms) clock tick:698510.
SQL
>INSERT INTO T
2   VALUES (3, 'C', 30);
INSERT INTO T
VALUES (3, 'C', 30)
1 rows affected
time used:
0.391(ms) clock tick:643640.
SQL
>COMMIT;
COMMIT;
time used:
11.657(ms) clock tick:19480330.
SQL
>SELECT * FROM T;
SELECT * FROM T;
ID              NAME            AGE
1       1       A       10
2       2       B       20
3       3       C       30
3 rows got
time used:
0.448(ms) clock tick:739460.

  建立了一个测试表之后,下面看看达梦是否采用行级锁来锁定记录:

SQL>UPDATE T
2   SET AGE = 20
3   WHERE ID = 1;
UPDATE T
SET AGE = 20
WHERE ID = 1;
1 rows affected
time used:
0.554(ms) clock tick:917730.

  需要注意,在进行这个测试的时候,需要确保AUTOCOMMIT是关闭的。

  在另外的会话中更新ID为2的记录,这时会话被阻塞:

SQL>UPDATE T
2   SET AGE = 30
3   WHERE ID = 2;

  在第一个会话中检查系统的锁状态:

SQL>SELECT TRX_ID, LTYPE, LMODE, BLOCKED, TABLE_ID, ROW_ID
2   FROM SYSTEM.SYSDBA.V$LOCK
3   WHERE LTYPE != 'DICT';
SELECT TRX_ID, LTYPE, LMODE, BLOCKED, TABLE_ID, ROW_ID
FROM SYSTEM.SYSDBA.V$LOCK
WHERE LTYPE != 'DICT';
TRX_ID      LTYPE    LMODE   BLOCKED  TABLE_ID   ROW_ID
1   1245     TABLE   IX      0        1026       0x0000000000000000
2   1246     TABLE   IX      0        1026       0x0000000000000000
3   1245     ROW     X       0        1026       0x000000000033FD31
4   1246     ROW     S       1        1026       0x000000000033FD31
4 rows got
time used:
22.091(ms) clock tick:36925520.

  可以看到,事务ID为1246并不是由于要获取独占锁而被阻塞,而是在查询到被修改的记录时被锁定。

  也就是说,在达梦数据库中,写是阻塞读的。

  如果要避免这种情况的产生,可以建立ID列上的索引。

  首先,会话1提交是否锁:

SQL>COMMIT;
COMMIT;
time used:
50.458(ms) clock tick:84356900.

  这时会话2上UPDATE操作成功:

1 rows affected
time used:
6652041.104(ms) clock tick:3358507298.

  下面再次检查当前的锁信息:

SQL>SELECT TRX_ID, LTYPE, LMODE, BLOCKED, TABLE_ID, ROW_ID
2   FROM SYSTEM.SYSDBA.V$LOCK
3   WHERE LTYPE != 'DICT';
SELECT TRX_ID, LTYPE, LMODE, BLOCKED, TABLE_ID, ROW_ID
FROM SYSTEM.SYSDBA.V$LOCK
WHERE LTYPE != 'DICT';
TRX_ID      LTYPE     LMODE   BLOCKED   TABLE_ID    ROW_ID
  
1    1246     TABLE   IX      0         1026        0x0000000000000000
  
2    1246     ROW     X       0         1026        0x000000000033FD32
2 rows got
time used:
0.405(ms) clock tick:668360.

  刚才被阻塞的事务1246现在获得了独占锁,可以放心当前锁定的行并不是1245锁定的记录。看来达梦中实现的确实是行级锁,不过达梦并没有解决读写相互锁定的问题。为了避免刚才的现象,在ID列增加索引:

SQL>COMMIT;
COMMIT;
time used:
11.548(ms) clock tick:19298280.
SQL
>CREATE INDEX IND_T_ID
2   ON T(ID);
CREATE INDEX IND_T_ID
ON T(ID);
time used:
36.302(ms) clock tick:60422530.

  下面再次执行刚才的操作:

SQL>UPDATE T
2   SET AGE = 30
3   WHERE ID = 1;
UPDATE T
SET AGE = 30
WHERE ID = 1;
1 rows affected
time used:
38.401(ms) clock tick:64196100.

  会话一的UPDATE成功。

SQL>UPDATE T
2   SET AGE = 40
3   WHERE ID = 2;
UPDATE T
SET AGE = 40
WHERE ID = 2;
1 rows affected
time used:
0.467(ms) clock tick:772190.

  而会话二执行UPDATE也成功了。

  这时再次检查锁信息:

SQL>SELECT TRX_ID, LTYPE, LMODE, BLOCKED, TABLE_ID, ROW_ID
2   FROM SYSTEM.SYSDBA.V$LOCK
3   WHERE LTYPE != 'DICT';
SELECT TRX_ID, LTYPE, LMODE, BLOCKED, TABLE_ID, ROW_ID
FROM SYSTEM.SYSDBA.V$LOCK
WHERE LTYPE != 'DICT';
TRX_ID      LTYPE    LMODE   BLOCKED  TABLE_ID   ROW_ID
1    1247    TABLE   IX      0        1026       0x0000000000000000
2    1249    TABLE   IX      0        1026       0x0000000000000000
3    1249    ROW     X       0        1026       0x000000000033FD32
4    1247    ROW     X       0        1026       0x000000000033FD31
4 rows got
time used:
0.435(ms) clock tick:717070.

  现在可以清晰的看到,两个并发的事务分别锁定同一张表的两条不同的记录。这说明达梦中实现了行级锁定。虽然这个动态视图提供的信息如此直观和清晰,甚至在Oracle中我们都不知道具体锁定了哪条记录,但是这也暴露了一个问题。就是在达梦数据库中锁是一种资源,数据库需要记录每条锁定的记录。当数据量过大的时候,为了避免消耗更多的资源,达梦数据库会采用锁升级的策略:

SQL>CREATE TABLE T_RECORD
2   (ID NUMBER);
CREATE TABLE T_RECORD
(ID
NUMBER);
time used:
11.990(ms) clock tick:19871840.
SQL
>INSERT INTO T_RECORD
2   SELECT ROWNUM FROM SYSTEM.SYSDBA.DUAL
3   CONNECT BY ROWNUM < 1000000;
INSERT INTO T_RECORD
SELECT ROWNUM FROM SYSTEM.SYSDBA.DUAL
CONNECT
BY ROWNUM < 1000000;
1000000 rows affected
time used:
75496.092(ms) clock tick:1644487636.
SQL
>COMMIT;
COMMIT;
time used:
41.571(ms) clock tick:69449680.
SQL
>SELECT COUNT(*) FROM SYSTEM.SYSDBA.V$LOCK;
SELECT COUNT(*) FROM SYSTEM.SYSDBA.V$LOCK;

1       1
1 rows got
time used:
0.395(ms) clock tick:651870.
SQL
>UPDATE T_RECORD
2   SET ID = ID + 1;
UPDATE T_RECORD
SET ID = ID + 1;
1000000 rows affected
time used:
15544.108(ms) clock tick:186236054.
SQL
>SELECT COUNT(*) FROM SYSTEM.SYSDBA.V$LOCK;
SELECT COUNT(*) FROM SYSTEM.SYSDBA.V$LOCK;

1       803
1 rows got
time used:
2.850(ms) clock tick:4754460.
SQL
>SELECT LTYPE, LMODE, COUNT(*)
2   FROM SYSTEM.SYSDBA.V$LOCK
3   WHERE LTYPE != 'DICT'
4   GROUP BY LTYPE, LMODE;
SELECT LTYPE, LMODE, COUNT(*)
FROM SYSTEM.SYSDBA.V$LOCK
WHERE LTYPE != 'DICT'
GROUP BY LTYPE, LMODE;
LTYPE           LMODE
1       TABLE   IX      1
2       TABLE   X       1
3       ROW     X       800
3 rows got
time used:
3.446(ms) clock tick:5750600.

  可以看到,在获取了800个行级锁后,数据库自动将行级锁升级为表级锁。

  在Oracle中锁并不是一种昂贵的资源,因此不会出现锁升级的情况。在达梦中,由于锁是一种资源,因此为了避免大量的持有锁,达梦采用了升级锁的方法,虽然这会在一定情况下影响并发性,但是这是资源权衡的结果,SQLSERVER等数据库也是这样实现的。

  不过达梦数据库中,似乎读并不阻塞写,尝试建立一个100W记录的表,在一个会话先开始一个SELECT * FROM TABLE的语法,然后在另一个会话执行一个UPDATE全表的语句。由于SELECT需要将结果打印到屏幕,因此SELECT语句在UPDATE之后完成,这显然说明SELECT并没有阻塞UPDATE。而且可以肯定SELECT是发生在UPDATE之前,因为再次执行同样的SELECT语句就会被UPDATE更新操作锁住。

  总的来说,达梦数据库的锁机制虽然不能和Oracle的比,但是也已经不错了,可以实现行级锁定,而且读并不阻塞写。而且达梦数据库还能实现多版本一致性读,不过要实现这种方式需要修改初始化参数配置中的默认设置,而且可能会对性能造成一定的影响。

0
相关文章