注册
“记录超长”问题分析与处理方法
专栏/技术分享/ 文章详情 /

“记录超长”问题分析与处理方法

携书问路 2023/08/29 3658 3 0
摘要

“记录超长”问题分析与处理方法

有时候在做数据库移植或应用开发测试过程中会遇到“记录超长”的报错,例如:
image.png

这个报错其实根据字面意思我们可能大概知道是数据的长度超长了,对达梦数据库比较熟悉的使用者其实是知道该如何处理的,一般来说有以下两种方法:

  1. 重新初始化实例,设置更大的页大小
  2. 对报错的表开启超长记录

下面我们通过举例来讨论这个记录超长的具体原因并进行分析,分析过程中我们要提到以下几个概念:

  1. 页大小(page_size)
  2. 伪列
  3. 超长记录
  4. 行内数据与行外数据
  5. 配置参数 LOB_MAX_INROW_LEN

页大小
页大小是初始化数据库时的参数,会影响字符长度(如 char/varchar)的实际最大长度和每条记录的最大长度。例如:第一,当页大小为 8K 时,对于字符类型实际最大长度为 3900 字节(不同页大小影响字符类型实际的最大长度是不一样的,见下图所示)
image.png
第二,行内记录长度最大长度不超过页大小的一半,这个是大前提。了解这两个前提对于理解以下内容比较关键,为了方便说明问题,我们使用默认的页大小 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 个 a 报错可以理解,因为超过 8k 页的约 3900 的最大长度限制了,那为什么插入 3880 个 a 也报错呢?其实我们知道达梦数据库中会有两个伪列,ROWID 和 TRXID,分别来看一下这两个伪列的长度:

select lengthb(rowid),lengthb(trxid),* from dual

image.png
这里我们可以大概算一下,除此之外,varchar 类型由于是变长类型,且实际数据长度大于 128,还需要 2 个字节表示长度,所以 3880+20+2=3902>3900 报错了。
超长记录
假如我们就是需要插入超过限制的大小,那么我们可以开启超长记录,那么就可以解除这个限制,因为超长的记录以 blob 的形式存在行外了。超长记录存储功能是指当 DM 行存储的记录长度超过页大小一半时,是否尝试将过长的变长字符串转换为行外 BLOB 存储。
image.png
下面我们来使用超长记录存储功能:

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;

image.png
dump()函数返回值:依次是数据类型代码、字节长度、表达式在系统内部存储字节。其中,数据类型代码有:
2 代表 VARCHAR、7 代表 INT、8 代表 BIGINT、9 代表 DEC、10 代表 FLOAT、11 代表DOUBLE、12 代表 BLOB、14 代表 DATE、15 代表 TIME、16 代表 DATETIME、18 代表VARBINARY、19 代表 CLOB、20 代表 INTERVAL YEAR MONTH、21 代表 INTERVAL DAY TIME、22 代表 TIME WITH ZONE、23 代表 DATE TIME WITH ZONE、28 代表 ROWID。
可以看到行内行外标记为行外,而行内长度为 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;

image.png
对比可以看出对于 varchar 类型定义的表,超长记录不会自动存储到行外,除非开启超长记录。而 lob 类型定义的表,会根据实际数据长度自动决定存储在行内还是行外,当然这里对于 lob 类型的数据,是否存在行内由参数 LOB_MAX_INROW_LEN 来控制。
LOB类型行内存储最大长度
LOB_MAX_INROW_LEN 默认为 900,决定了大字段类型能放进行行内存储的最大长度(但并不绝对,还与“每条记录总长度不超过页大小一半”有关),与上述讨论的第三点息息相关,超过该值则作为行外存储,没超过则作为行内存储。

select * from v$dm_ini where para_name like 'LOB%';

image.png

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;

image.png
可以看到,改参数值可以控制是否存在行内的边界长度。
那回到上述第三点讨论中,这个插入的 varchar 长度是 100,并没有超过 LOB_MAX_INROW_LEN 默认值,为什么也是存在行外呢?

call insert_table(39,100);-- 正常插入,行外

这里我们可以大致计算一下,如果这条记录存在行内的话 39*100+20=3920,也超过了 3900 所以只能存到行外去了。
总结:
综上所述,达梦数据库中报错“记录超长”与上述因素都有关系,其中页大小是影响最大的一个因素,如果业务场景中涉及的表都会比较“宽”,那么建议直接将页大小设置大一些,可以避免一些记录超长的问题,但是相对来说也会更加浪费空间一些,这里需要 dba 做一些权衡。

评论
后发表回复

作者

文章

阅读量

获赞

扫一扫
联系客服