在达梦数据库上,有时候在做数据库移植或应用开发测试过程中会遇到“记录超长”的报错,例如:
这个报错其实根据字面意思我们可能大概知道是数据的长度超长了,对达梦数据库比较熟悉的使用者其实是知道该如何处理的,一般来说有以下两种方法:
下面我们通过举例来讨论这个记录超长的具体原因并进行分析,分析过程中我们要提到以下几个概念:
页大小是初始化数据库时的参数,会影响字符长度(如char/varchar)的实际最大长度和每条记录的最大长度。例如:
第一,当页大小为8K时,对于字符类型实际最大长度为3900字节(不同页大小影响字符类型实际的最大长度是不一样的,见下图所示)
第二,行内记录长度最大长度不超过页大小的一半,这个是大前提。
了解这两个前提对于理解以下内容比较关键,为了方便说明问题,我们使用默认的页大小page_size=8K
举个例子:
create table test_singlecol( col varchar(8000));
insert into test_singlecol values(repeat('a',4000)); --报错,因为超过3900了
insert into test_singlecol values(repeat('a',3880)); --报错,因为超过3900了
insert into test_singlecol values(repeat('a',3879)); --正常插入,还有20个自己被内置的存储占用,例如伪列rowid,trxid还有null标记等
插入4000报错可以理解,因为超过8k页的约3900的限制了,那为什么3880页报错了?其实我们知道达梦数据库中会有两个伪列,ROWID和TRXID,分别来看一下这两个伪列的长度:
select lengthb(rowid),lengthb(trxid),* from dual;
这里我们可以大概算一下,除此之外,varchar类型由于是变长类型,且实际数据长度大于128,还需要2个字节表示长度,所以3880+20+2=3902>3900报错了。
假如我们就是需要插入超过限制的大小,那么我们可以开启超长记录,那么就可以解除这个限制,因为超长的记录以blob的形式存在行外了。
超长记录存储功能是指当 DM 行存储的记录长度超过页大小一半时,是否尝试将过长的变长字符串转换为行外 BLOB 存储。
ALTER TABLE test_singlecol ENABLE USING LONG ROW;
insert into test_singlecol values(repeat('a',3880)); --正常插入
insert into test_singlecol values(repeat('a',4000)); --正常插入
在基表中,当没有指定 USING LONG ROW 存储选项时,插入 VARCHAR 数据类型的实际最大存储长度由数据库页面大小决定,具体最大长度算法如下表所示,如果指定了 USING LONG ROW 存储选项,则插入 VARCHAR 数据类型的长度不受数据库页面大小限制。VARCHAR 类型在表达式计算中的长度上限不受页面大小限制,为 32767;
对于像TEXT,CLOB之类的大字段类型而言,当实际数据长度较小时就与其他字段紧凑的放在一起,称之为行内数据,如果实际数据长度较长时,实际数据就单独放在某些数据页中,而与其他字段一起存储的只是大字段头。
drop table if exists test_text;
create table test_text(col text);
insert into test_text values(repeat('a',901));
commit;
select dump(col) from test_text;
可以看到行内行外标记为行外,而行内长度为47,这个47也就是大字段头的长度
因为我们知道记录一旦超长被记录到行外lob中,则行内字段记录的是这个数据的大字段头信息,是固定长度47。
对比如下两种插入:
对于varchar类型表定义:
call create_table(39,'varchar(4000)');
call insert_table(39,4000);-- -2665: 记录超长
alter table "SYSDBA"."TEST_39COL" enable using long row;
call insert_table(39,4000);--正常插入
对于text类型表定义:
drop table if exists TEST_39COL;
call create_table(39,'text');
call insert_table(39,50);-- 正常插入,行内
call insert_table(39,100);-- 正常插入,行外
call insert_table(39,4000);-- 正常插入,行外
select dump(c1),dump(c2),dump(c3),dump(c4) from TEST_39COL;
对比可以看出对于varchar类型定义的表,超长记录不会自动存储到行外,除非开启超长记录。而lob类型定义的表,会根据实际数据长度自动决定存储在行内还是行外,当然这里对于lob类型的数据,是否存在行内由参数LOB_MAX_INROW_LEN来控制。
LOB_MAX_INROW_LEN默认为900,决定了大字段类型能放进行行内存储的最大长度(但并不绝对,还与“每条记录总长度不超过页大小一般”有关),与上述讨论的第三点息息相关,超过该值则作为行外存储,没超过则作为行内存储。
select * from v$dm_ini where para_name like 'LOB%';
create table test_lob(col text);
insert into test_lob values(repeat('a',901));--行外
insert into test_lob values(repeat('a',900));--行内
commit;
select dump(col) from test_lob;
可以看到,改参数值可以控制是否存在行内的边界长度。
那回到上述第三点讨论中,这个插入的varchar长度是100,并没有超过LOB_MAX_INROW_LEN默认值,为什么也是存在行外呢?这里我们可以大致计算一下,如果这条记录存在行内的话39*100+20=3920,也超过了3900所以只能存到行外去了。
综上所述,达梦数据库中报错“记录超长”与上述因素都有关系,其中页大小是影响最大的一个因素,如果业务场景中涉及的表都会比较“宽”,那么建议直接将页大小设置大一些,可以避免一些记录超长的问题,但是相对来说也会更加浪费空间一些,这里需要dba做一些权衡。
文章
阅读量
获赞