梁敬彬梁敬弘兄弟出品
达梦数据库在COUNT(*)操作上表现出色,本文通过实验数据展示并分析其性能特点。
一、实验现象:COUNT(*)的高性能表现
1.1 初始测试(约2800条记录)
drop table t purge;
create table t as select * from dba_objects;
SQL> insert into t select * from t;
影响行数 1401
已用时间: 32.849(毫秒). 执行号:256.
SQL> commit;
操作已执行
已用时间: 2.270(毫秒). 执行号:257.
SQL> select count(*) from t;
行号 COUNT(*)
---------- --------------------
1 2802
已用时间: 0.239(毫秒). 执行号:288.
1.2 数据量增加后测试(约570万条记录)
SQL> insert into t select * from t;
影响行数 2869248
已用时间: 00:00:45.028. 执行号:289.
SQL> commit;
操作已执行
已用时间: 8.689(毫秒). 执行号:290.
SQL> select count(*) from t;
行号 COUNT(*)
---------- --------------------
1 5738496
已用时间: 0.213(毫秒). 执行号:291.
关键观察:数据量增长超过1000倍(从2800条到570万条),而COUNT(*)执行时间几乎不变(保持在0.2毫秒左右)。
二、原理分析:内部计数器机制
原文推测达梦数据库的高效COUNT(*)与两个因素可能有关:
索引组织表结构:默认的表存储形式
内部计数器:表内部维护记录数量
2.1 验证索引组织表因素
通过创建堆表(NOBRANCH选项)测试:
SQL> drop table t purge;
操作已执行
已用时间: 30.886(毫秒). 执行号:295.
SQL> create table t STORAGE (NOBRANCH) as select * from dba_objects;
操作已执行
SQL> insert into t select * from t;
影响行数 2869248
已用时间: 00:00:20.234. 执行号:330.
SQL> commit;
操作已执行
已用时间: 769.113(毫秒). 执行号:331.
SQL> select count(*) from t;
行号 COUNT(*)
---------- --------------------
1 5738496
已用时间: 0.299(毫秒). 执行号:332.
结论:堆表的COUNT(*)性能同样很高,说明高性能并非由索引组织表结构带来。
2.2 验证内部计数器因素
通过禁用计数器选项(WITHOUT COUNTER)测试:
create table t storage(without counter) as select * from dba_objects;
SQL> select count(*) from t;
行号 COUNT(*)
---------- --------------------
1 2802
已用时间: 18.292(毫秒). 执行号:340.
SQL> insert into t select * from t;
影响行数 2869248
已用时间: 00:00:22.171. 执行号:371.
SQL> commit;
操作已执行
已用时间: 54.104(毫秒). 执行号:372.
SQL> select count(*) from t;
行号 COUNT(*)
---------- --------------------
1 5738496
已用时间: 00:00:01.436. 执行号:373.
SQL> insert into t select * from t;
影响行数 5738496
已用时间: 00:00:23.270. 执行号:374.
SQL> commit;
操作已执行
已用时间: 16.629(毫秒). 执行号:375.
SQL> select count(*) from t;
行号 COUNT(*)
---------- --------------------
1 11476992
已用时间: 00:00:03.256. 执行号:376.
关键观察:
禁用计数器后,初始COUNT()性能从0.2毫秒变为18.292毫秒
数据量增加到570万条时,耗时增至1.436秒
数据量增加到1147万条时,耗时增至3.256秒
性能随数据量呈线性下降趋势
结论:内部计数器是达梦数据库COUNT()高性能的关键因素。
2.3 查看表计数器状态
通过系统存储过程可查看表是否启用计数器:
SQL> sp_tabledef('LJB','T');
行号 COLUMN_VALUE
-------------------------------------------------------------------------------------------------------------------------------------------
1 CREATE TABLE "LJB"."T" (...) STORAGE(ON "MAIN", CLUSTERBTR, WITHOUT COUNTER);
已用时间: 172.222(毫秒). 执行号:377.
三、应用建议
基于实验结果,可得出以下应用建议:
默认使用计数器:达梦数据库默认启用计数器,这对COUNT(*)查询非常有利
特定场景禁用计数器:如果业务中很少使用COUNT(*)但频繁进行大批量更新操作,可考虑禁用计数器以提高写入性能
创建表时指定计数器选项:
-- 启用计数器(默认)
CREATE TABLE table_name (...);
-- 禁用计数器
CREATE TABLE table_name (...) STORAGE(WITHOUT COUNTER);
利用计数器特性优化查询:当需要获取表总行数时,优先使用简单的COUNT(*)而非带条件的查询
四、总结
达梦数据库通过内部计数器机制实现了高效的COUNT(*)操作,使其在数据量增长的情况下仍保持稳定的高性能。实验证明:
无论是索引组织表还是堆表,只要启用计数器,COUNT()性能都非常优异
禁用计数器后,COUNT()性能随数据量增长而线性下降
通过sp_tabledef存储过程可以查询表的计数器状态
这一特性使达梦数据库在需要频繁统计表行数的场景中具有显著优势,特别是对于大型表的统计操作,性能表现尤为突出。
国内一线知名数据库专家、畅销书《收获,不止Oracle》作者梁敬彬老师,做客【达梦会客厅】
公众号:收获不止数据库