目录
一、环境信息
|------|------------------------------------------------------------------------------------------------------------------------------|
| 名称 | 值 |
| CPU | 12th Gen Intel(R) Core(TM) i7-12700H |
| 操作系统 | CentOS Linux release 7.9.2009 (Core) |
| 内存 | 4G |
| 逻辑核数 | 4 |
| DM版本 | 1 DM Database Server 64 V8 2 DB Version: 0x7000d 3 03134284336-20250218-260144-20132 4 Msg Version: 32 5 Gsu level(5) cnt: 0 |
二、介绍
最近在使用达梦数据库的时候,发现删除数据后,过一段时间,表大小不回缩,判断达梦在删除数据时,和PG数据库类似,不会立马删除数据,只是打了标记,需要进行空洞率清理才可以释放空间,清理前占用1.4T,重建表后为140G,所以写了一个匿名块实现表大小快速估算,因为很多生产环境是不允许创建存过的。
三、功能介绍
1、定义
sql
--SET SERVEROUTPUT ON
/*此匿名块不计算字段和字段的字节填充(字节对齐)。*/
DECLARE
UserName VARCHAR(50) := 'SYSDBA';
TabName VARCHAR(50) := 'SUN';
TYPE HashTabT IS TABLE OF INT INDEX BY VARCHAR2(50);
TYPE StrArrayT IS TABLE OF VARCHAR2(50) INDEX BY PLS_INTEGER;
TypeMap HashTabT; /*类型映射表。*/
ColSets StrArrayT; /*实时计算字段表。*/
ColSetsIdx INT := 0; /*实时计算字段表的索引。*/
ComputeFlag INT := -1; /*打了此标记的列,实际计算大小。*/
PartComputeFlag INT := -2; /*打了此标记的列,部分计算大小,根据列长度。*/
OneRowSize INT := 0; /*一行的字节数总和,不包含 ComputeFlag。*/
ColSize PLS_INTEGER := 0; /*一列的字节数。*/
TotalSize BIGINT := 0; /*表整体字节数。*/
CntSql VARCHAR(200) := 'SELECT COUNT(*) FROM '||UserName||'.'||TabName;
Cnt BIGINT := 0; /*表行数。*/
ComputeSql TEXT := 'SELECT SUM(';
ComputeSize BIGINT := 0; /*表需实时计算字段总和字节数。*/
BEGIN
DBMS_OUTPUT.ENABLE;
DBMS_OUTPUT.PUT_LINE('UserName : '||UserName);
DBMS_OUTPUT.PUT_LINE('TabName : '||TabName);
/*初始化HASH桶。*/
TypeMap('CHAR') := PartComputeFlag;
TypeMap('VARCHAR') := ComputeFlag;
TypeMap('VARCHAR2') := ComputeFlag;
TypeMap('TINYINT') := 1;
TypeMap('BIT') := 1;
TypeMap('BYTE') := 1;
TypeMap('SMALLINT') := 2;
TypeMap('INT') := 4;
TypeMap('BIGINT') := 8;
TypeMap('REAL') := 4;
TypeMap('FLOAT') := 8;
TypeMap('DOUBLE') := 8;
TypeMap('DOUBLE PRECISION') := 8;
TypeMap('DEC') := ComputeFlag; /*1~22 个字节 ,取个中间值*/
TypeMap('DECIMAL') := ComputeFlag; /*1~22 个字节 ,取个中间值*/
TypeMap('NUMERIC') := ComputeFlag; /*1~22 个字节 ,取个中间值*/
TypeMap('NUMBER') := ComputeFlag; /*1~22 个字节 ,取个中间值*/
TypeMap('BINARY') := ComputeFlag;
TypeMap('VARBINARY') := ComputeFlag;
TypeMap('DATE') := 3;
TypeMap('TIME') := 5;
TypeMap('TIMESTAMP') := 8;
TypeMap('DATETIME') := 8;
TypeMap('TIME WITH TIME ZONE') := 7;
TypeMap('TIMESTAMP WITH TIME ZONE') := 10;
TypeMap('DATETIME WITH TIME ZONE') := 10; /*参考类型 TIMESTAMP WITH TIME ZONE*/
TypeMap('INTERVAL YEAR') := 12;
TypeMap('INTERVAL MONTH') := 12;
TypeMap('INTERVAL YEAR TO MONTH') := 12;
TypeMap('INTERVAL DAY') := 24;
TypeMap('INTERVAL HOUR') := 24;
TypeMap('INTERVAL MINUTE') := 24;
TypeMap('INTERVAL SECOND') := 24;
TypeMap('INTERVAL DAY TO HOUR') := 24;
TypeMap('INTERVAL DAY TO MINUTE') := 24;
TypeMap('INTERVAL DAY TO SECOND') := 24;
TypeMap('INTERVAL HOUR TO MINUTE') := 24;
TypeMap('INTERVAL HOUR TO SECOND') := 24;
TypeMap('INTERVAL MINUTE TO SECOND') := 24;
TypeMap('BLOB') := ComputeFlag;
TypeMap('IMAGE') := ComputeFlag;
TypeMap('LONGVARBINARY') := ComputeFlag;
TypeMap('CLOB') := ComputeFlag;
TypeMap('TEXT') := ComputeFlag;
TypeMap('LONGVARCHAR') := ComputeFlag;
/*获取一列的字节数,不包含需计算列。*/
FOR I IN (SELECT COLUMN_ID,COLUMN_NAME,DATA_TYPE,DATA_LENGTH FROM DBA_TAB_COLUMNS WHERE OWNER = UserName AND TABLE_NAME = TabName ORDER BY COLUMN_ID) LOOP
/*判断列类型是否存在。*/
IF !(TypeMap.EXISTS(I.DATA_TYPE)) THEN
DBMS_OUTPUT.PUT_LINE('ColId : '||I.COLUMN_ID);
DBMS_OUTPUT.PUT_LINE('Col : '||I.COLUMN_NAME);
DBMS_OUTPUT.PUT_LINE('Unknown Type : '||I.DATA_TYPE);
DBMS_OUTPUT.PUT_LINE('Len : '||I.DATA_LENGTH);
RETURN;
END IF;
ColSize = TypeMap(I.DATA_TYPE);
/*根据不同类型进行不同的处理。*/
IF ColSize = PartComputeFlag THEN
ColSize := I.DATA_LENGTH;
ELSIF ColSize = ComputeFlag THEN
ColSize := 0;
ColSets(ColSetsIdx) := I.COLUMN_NAME;
ColSetsIdx := ColSetsIdx + 1;
ELSE
ColSize := TypeMap(I.DATA_TYPE);
END IF;
OneRowSize := OneRowSize + ColSize;
-- DBMS_OUTPUT.PUT_LINE('ColId : '||I.COLUMN_ID);
-- DBMS_OUTPUT.PUT_LINE('Col : '||I.COLUMN_NAME);
-- DBMS_OUTPUT.PUT_LINE('Type : '||I.DATA_TYPE);
-- DBMS_OUTPUT.PUT_LINE('Len : '||I.DATA_LENGTH);
-- DBMS_OUTPUT.PUT_LINE('ColSize : '||ColSize);
-- DBMS_OUTPUT.PUT_LINE('======================================');
END LOOP;
DBMS_OUTPUT.PUT_LINE('OneRowSize(Bytes) : '||OneRowSize);
/*OneRowSize 不可能为0,说明模式或表不存在。*/
IF OneRowSize = 0 THEN
DBMS_OUTPUT.PUT_LINE('The user or table does not exist.');
RETURN;
END IF;
/*计算表的总条数。*/
EXECUTE IMMEDIATE CntSql INTO Cnt;
DBMS_OUTPUT.PUT_LINE('Cnt : '||Cnt);
-- DBMS_OUTPUT.PUT_LINE('ColSetsIdx : '||ColSetsIdx);
/*判断是否需要进行实际计算。*/
IF (ColSetsIdx > 0) AND (Cnt > 0) THEN
ColSetsIdx := ColSetsIdx - 1;
FOR Y IN 0..ColSetsIdx LOOP
ComputeSql := ComputeSql||'NVL(LENGTHB('||ColSets(Y)||'),0)';
IF Y != ColSetsIdx THEN
ComputeSql := ComputeSql||' + ';
END IF;
END LOOP;
ComputeSql := ComputeSql||' ) AS TOTAL_SIZE FROM '||UserName||'.'||TabName;
-- DBMS_OUTPUT.PUT_LINE('ComputeSql : '||ComputeSql);
EXECUTE IMMEDIATE ComputeSql INTO ComputeSize;
END IF;
DBMS_OUTPUT.PUT_LINE('ComputeSize(Bytes) : '||ComputeSize);
/*所有结果汇总。*/
TotalSize := OneRowSize * Cnt + ComputeSize;
DBMS_OUTPUT.PUT_LINE('TotalSize(G) : '|| trunc(TotalSize / 1024.0 / 1024 / 1024 , 3));
END;
2、自调整
某些变长类型我们是实际计算的,大家可以根据实际情况给一个估算值,方便快速计算出结果。
开头的用户名和表名根据实际情况调整。
3、提示
只做了插入的表,和视图查询出的实际大小写会有差异,因为底层计算方式可能不同,例如可能会计算:
(1)字段和字段的字节填充,为了字节对齐,提升效率。
(2)块头。
(3)行头。
四、实验
1、测试数据
sql
DROP TABLE IF EXISTS SUN;
CREATE TABLE SUN (T0 SMALLINT,
T1 INT,
T2 BIGINT,
T3 NUMERIC(20,6),
T4 DECIMAL(30,8),
T5 REAL,
T6 DOUBLE PRECISION,
T7 SMALLINT,
T8 INT,
T9 BIGINT,
T10 CHAR(10),
T11 VARCHAR(4000),
T12 TEXT,
T13 BLOB,
T14 FLOAT,
T15 TIMESTAMP,
T16 TIMESTAMP WITH TIME ZONE,
T17 DATE,
T18 TIME,
T19 TIME WITH TIME ZONE,
T20 INTERVAL YEAR,
T21 CHAR(1),
T22 VARCHAR(100),
T23 CHAR(20),
T24 BIGINT);
INSERT INTO SUN (T0,T1,T2,T3,T4,T5,T6,T10,T11,T12,T13,T14,T15,T16,T17,T18,T19,T20,T21,T22,T23,T24) VALUES
(1,2,918118131,1.1, NULL, 0 ,0.123456789123456789 ,'1','SUN','BJ','18',1.1,'2025-09-26 09:16:25.674136','2025-09-26 09:16:25.674136+08','1995-09-18','13:36:00.036269','15:09:34.266988+08','1 YEAR 1 MONTH 1 DAY 01:01:01.1','T','LXG','A',1);
INSERT INTO SUN (T0,T1,T2,T3,T4,T5,T6,T10,T11,T12,T13,T14,T15,T16,T17,T18,T19,T20,T21,T22,T23,T24) VALUES
(1,2,918118131,0, 12345678910111213.12345678,0.12345678910,0 ,'2','LZL','WZ','19',0,'2025-09-25 09:16:25.674136','2025-09-26 09:16:25.674136+09','1995-05-17','13:36:00.000009','15:09:34.000008+08','2 YEAR 1 MONTH 1 DAY 01:01:01.01','F','LLX','B',2);
INSERT INTO SUN (T0,T1,T2,T3,T4,T5,T6,T10,T11,T12,T13,T14,T15,T16,T17,T18,T19,T20,T21,T22,T23,T24) VALUES
(1,2,918118131,0.12345, 10000000000000003.12345678,0.123456789 ,1234567893.12456789 ,'3','CLX','BJWZQZ','20',22,'2025-09-26 09:16:25.123456','2025-09-26 10:16:25.674136+08','1995-01-18','01:30:00.036269','12:09:34.266988+08','1 YEAR 2 MONTH 1 DAY 01:01:01.001','T','ZXJ','C',3);
INSERT INTO SUN (T0,T1,T2,T3,T4,T5,T6,T10,T11,T12,T13,T14,T15,T16,T17,T18,T19,T20,T21,T22,T23,T24) VALUES
(1,2,918118131,1234.1234, 10000000005000000.00050000,123.4567 ,1234567894.123456789 ,'4','LDQ','WZ',NULL,1.66,'2025-09-26 09:06:25.674136','2025-09-26 09:16:25.674136+08','2024-01-31','05:30:00.036269','01:09:34.266988+08','1 YEAR 1 MONTH 2 DAY 01:01:01.0001','T','LDQ','D',11);
INSERT INTO SUN (T0,T1,T2,T3,T4,T5,T6,T10,T11,T12,T13,T14,T15,T16,T17,T18,T19,T20,T21,T22,T23,T24) VALUES
(1,2,918118131,12345.123, 12345678910111213.12345678,1234.5678910 ,1234567895.123456789 ,'5',NULL,NULL,'30',0.55555,'2025-11-26 09:06:25.674136','2025-09-26 09:16:25.674136+08','1995-09-18','13:36:00.036269','00:09:34.266988+08','1 YEAR 1 MONTH 1 DAY 02:01:01','T','CZG','E',22);
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
INSERT INTO SUN SELECT * FROM SUN;
COMMIT;
2、视图查看表大小
sql
SELECT
OWNER,
SEGMENT_NAME,
SEGMENT_TYPE,
ROUND(SUM(BYTES) / 1024.0 / 1024 / 1024, 3) AS TOTAL_SIZE
FROM DBA_SEGMENTS
WHERE
OWNER = 'SYSDBA' AND SEGMENT_NAME = 'SUN'
GROUP BY OWNER, SEGMENT_NAME, SEGMENT_TYPE;
行号 OWNER SEGMENT_NAME SEGMENT_TYPE TOTAL_SIZE
---------- ------ ------------ ------------ ----------
1 SYSDBA SUN TABLE 0.059
已用时间: 771.536(毫秒). 执行号:10112.
3、匿名块计算表大小
sql
SET SERVEROUTPUT ON
/*此匿名块不计算字段和字段的字节填充(字节对齐)。*/
DECLARE
UserName VARCHAR(50) := 'SYSDBA';
TabName VARCHAR(50) := 'SUN';
TYPE HashTabT IS TABLE OF INT INDEX BY VARCHAR2(50);
TYPE StrArrayT IS TABLE OF VARCHAR2(50) INDEX BY PLS_INTEGER;
TypeMap HashTabT; /*类型映射表。*/
ColSets StrArrayT; /*实时计算字段表。*/
ColSetsIdx INT := 0; /*实时计算字段表的索引。*/
ComputeFlag INT := -1; /*打了此标记的列,实际计算大小。*/
PartComputeFlag INT := -2; /*打了此标记的列,部分计算大小,根据列长度。*/
OneRowSize INT := 0; /*一行的字节数总和,不包含 ComputeFlag。*/
ColSize PLS_INTEGER := 0; /*一列的字节数。*/
TotalSize BIGINT := 0; /*表整体字节数。*/
CntSql VARCHAR(200) := 'SELECT COUNT(*) FROM '||UserName||'.'||TabName;
Cnt BIGINT := 0; /*表行数。*/
ComputeSql TEXT := 'SELECT SUM(';
ComputeSize BIGINT := 0; /*表需实时计算字段总和字节数。*/
BEGIN
DBMS_OUTPUT.ENABLE;
DBMS_OUTPUT.PUT_LINE('UserName : '||UserName);
DBMS_OUTPUT.PUT_LINE('TabName : '||TabName);
/*初始化HASH桶。*/
TypeMap('CHAR') := PartComputeFlag;
TypeMap('VARCHAR') := ComputeFlag;
TypeMap('VARCHAR2') := ComputeFlag;
TypeMap('TINYINT') := 1;
TypeMap('BIT') := 1;
TypeMap('BYTE') := 1;
TypeMap('SMALLINT') := 2;
TypeMap('INT') := 4;
TypeMap('BIGINT') := 8;
TypeMap('REAL') := 4;
TypeMap('FLOAT') := 8;
TypeMap('DOUBLE') := 8;
TypeMap('DOUBLE PRECISION') := 8;
TypeMap('DEC') := ComputeFlag; /*1~22 个字节 ,取个中间值*/
TypeMap('DECIMAL') := ComputeFlag; /*1~22 个字节 ,取个中间值*/
TypeMap('NUMERIC') := ComputeFlag; /*1~22 个字节 ,取个中间值*/
TypeMap('NUMBER') := ComputeFlag; /*1~22 个字节 ,取个中间值*/
TypeMap('BINARY') := ComputeFlag;
TypeMap('VARBINARY') := ComputeFlag;
TypeMap('DATE') := 3;
TypeMap('TIME') := 5;
TypeMap('TIMESTAMP') := 8;
TypeMap('DATETIME') := 8;
TypeMap('TIME WITH TIME ZONE') := 7;
TypeMap('TIMESTAMP WITH TIME ZONE') := 10;
TypeMap('DATETIME WITH TIME ZONE') := 10; /*参考类型 TIMESTAMP WITH TIME ZONE*/
TypeMap('INTERVAL YEAR') := 12;
TypeMap('INTERVAL MONTH') := 12;
TypeMap('INTERVAL YEAR TO MONTH') := 12;
TypeMap('INTERVAL DAY') := 24;
TypeMap('INTERVAL HOUR') := 24;
TypeMap('INTERVAL MINUTE') := 24;
TypeMap('INTERVAL SECOND') := 24;
TypeMap('INTERVAL DAY TO HOUR') := 24;
TypeMap('INTERVAL DAY TO MINUTE') := 24;
TypeMap('INTERVAL DAY TO SECOND') := 24;
TypeMap('INTERVAL HOUR TO MINUTE') := 24;
TypeMap('INTERVAL HOUR TO SECOND') := 24;
TypeMap('INTERVAL MINUTE TO SECOND') := 24;
TypeMap('BLOB') := ComputeFlag;
TypeMap('IMAGE') := ComputeFlag;
TypeMap('LONGVARBINARY') := ComputeFlag;
TypeMap('CLOB') := ComputeFlag;
TypeMap('TEXT') := ComputeFlag;
TypeMap('LONGVARCHAR') := ComputeFlag;
/*获取一列的字节数,不包含需计算列。*/
FOR I IN (SELECT COLUMN_ID,COLUMN_NAME,DATA_TYPE,DATA_LENGTH FROM DBA_TAB_COLUMNS WHERE OWNER = UserName AND TABLE_NAME = TabName ORDER BY COLUMN_ID) LOOP
/*判断列类型是否存在。*/
IF !(TypeMap.EXISTS(I.DATA_TYPE)) THEN
DBMS_OUTPUT.PUT_LINE('ColId : '||I.COLUMN_ID);
DBMS_OUTPUT.PUT_LINE('Col : '||I.COLUMN_NAME);
DBMS_OUTPUT.PUT_LINE('Unknown Type : '||I.DATA_TYPE);
DBMS_OUTPUT.PUT_LINE('Len : '||I.DATA_LENGTH);
RETURN;
END IF;
ColSize = TypeMap(I.DATA_TYPE);
/*根据不同类型进行不同的处理。*/
IF ColSize = PartComputeFlag THEN
ColSize := I.DATA_LENGTH;
ELSIF ColSize = ComputeFlag THEN
ColSize := 0;
ColSets(ColSetsIdx) := I.COLUMN_NAME;
ColSetsIdx := ColSetsIdx + 1;
ELSE
ColSize := TypeMap(I.DATA_TYPE);
END IF;
OneRowSize := OneRowSize + ColSize;
-- DBMS_OUTPUT.PUT_LINE('ColId : '||I.COLUMN_ID);
-- DBMS_OUTPUT.PUT_LINE('Col : '||I.COLUMN_NAME);
-- DBMS_OUTPUT.PUT_LINE('Type : '||I.DATA_TYPE);
-- DBMS_OUTPUT.PUT_LINE('Len : '||I.DATA_LENGTH);
-- DBMS_OUTPUT.PUT_LINE('ColSize : '||ColSize);
-- DBMS_OUTPUT.PUT_LINE('======================================');
END LOOP;
DBMS_OUTPUT.PUT_LINE('OneRowSize(Bytes) : '||OneRowSize);
/*OneRowSize 不可能为0,说明模式或表不存在。*/
IF OneRowSize = 0 THEN
DBMS_OUTPUT.PUT_LINE('The user or table does not exist.');
RETURN;
END IF;
/*计算表的总条数。*/
EXECUTE IMMEDIATE CntSql INTO Cnt;
DBMS_OUTPUT.PUT_LINE('Cnt : '||Cnt);
-- DBMS_OUTPUT.PUT_LINE('ColSetsIdx : '||ColSetsIdx);
/*判断是否需要进行实际计算。*/
IF (ColSetsIdx > 0) AND (Cnt > 0) THEN
ColSetsIdx := ColSetsIdx - 1;
FOR Y IN 0..ColSetsIdx LOOP
ComputeSql := ComputeSql||'NVL(LENGTHB('||ColSets(Y)||'),0)';
IF Y != ColSetsIdx THEN
ComputeSql := ComputeSql||' + ';
END IF;
END LOOP;
ComputeSql := ComputeSql||' ) AS TOTAL_SIZE FROM '||UserName||'.'||TabName;
-- DBMS_OUTPUT.PUT_LINE('ComputeSql : '||ComputeSql);
EXECUTE IMMEDIATE ComputeSql INTO ComputeSize;
END IF;
DBMS_OUTPUT.PUT_LINE('ComputeSize(Bytes) : '||ComputeSize);
/*所有结果汇总。*/
TotalSize := OneRowSize * Cnt + ComputeSize;
DBMS_OUTPUT.PUT_LINE('TotalSize(G) : '|| trunc(TotalSize / 1024.0 / 1024 / 1024 , 3));
END;
/
UserName : SYSDBA
TabName : SUN
OneRowSize(Bytes) : 132
Cnt : 327680
ComputeSize(Bytes) : 12255232
TotalSize(G) : 0.051
DMSQL 过程已成功完成
已用时间: 00:00:01.160. 执行号:10113.
0.059和0.051相差不大,说明我们实现的相对准确。