☰
达梦数据库ALTER TABLE修改字段类型:风险、语法与实操
2026/11/2 4:07:49 网站建设 项目流程

1. 改字段类型这件事,先想清楚代价

上周有位同行发来一句话:“达梦数据库里一个VARCHAR(20)的字段要扩到VARCHAR(200),直接alter table ... modify会不会把生产表锁死?”这个问题问得很实在。凡是碰过生产库的人,看到“达梦数据库 alter table 修改字段类型”这几个字,脑子里先跳出来的通常不是语法,而是锁、依赖、回滚这几件事。语法本身十分钟就能记住,真正决定这件事能不能平稳落地的,是改动之前有没有把代价算清楚。

字段类型变更属于典型的“小需求、大影响”操作。业务方往往只提一句“这个字段不够用了”,落到数据库层面却可能牵动索引、约束、视图、存储过程、分区定义,甚至影响正在跑的批量任务。达梦数据库在这块既有 Oracle 风格的熟悉感,又带着自己的一套规则,比如长度单位、页大小限制、DDL 的提交行为,这些都跟其他数据库不完全一样。

这篇内容适合三类人看:刚接手达梦库、准备第一次改字段类型的开发;需要评估变更风险、给上线排窗口期的运维;以及从其他数据库迁移过来、习惯性敲ALTER COLUMN ... TYPE结果报语法错误的朋友。我会把语法边界、前置体检、四类典型改造的完整步骤、常见报错的处理思路都摊开讲,尽量做到看完就能照着做。文中涉及具体数值的地方,我会说明依据,拿不准的地方也会明确提醒你去核对对应版本的官方文档,不拍脑袋。

1.1 一个“扩长度”需求背后的真实工作量

很多人以为改字段类型就是一行 DDL 的事,实际做过几次就知道,一行命令背后往往跟着一串动作。以把VARCHAR(20)扩到VARCHAR(200)为例,听起来是无损操作,但如果这一列上有索引,你得确认索引键长度上限够不够;如果有视图引用了这一列,改完之后视图可能直接失效;如果有存储过程或触发器对该列做了类型敏感的运算,编译状态可能变成无效;如果表是分区表,还得确认所有分区的列定义是否会被一并处理。

更麻烦的是数据本身。加长一般不会丢数据,但收窄一定会。把VARCHAR(200)改回VARCHAR(50),只要有超过 50 个字符的记录,DDL 就会报错中断;把NUMBER(10,2)改成NUMBER(8,0),小数部分会被截断,超过整数位的数据直接溢出。这些都不是“理论上可能”,而是实际排查时最常撞上的场景。

我的习惯是,接到改类型的需求先不写 SQL,先问四个问题:这列现在存的是什么数据、最大长度或最大精度是多少、有哪些对象依赖它、出问题能不能回滚。这四个问题答完,工作量自然就清楚了。答不上来的,说明还没到能动手的时候。

1.2 达梦的 DDL 脾气:隐式提交加上排他锁

达梦数据库的 DDL 语句默认是隐式提交的,也就是说ALTER TABLE一旦执行,前面未提交的事务会被一并提交,语句本身也会立即生效,没有“先放在事务里看看效果再回滚”这种操作。这一点跟 Oracle 的行为接近,跟某些支持事务性 DDL 的数据库完全不同。所以改字段类型之前必须自己做好备份,别指望ROLLBACK能救回来。

锁的层面,ALTER TABLE ... MODIFY在执行期间会对目标表加排他锁,阻塞其他会话对该表的读写。表越小,锁的时间越短;表越大,锁的时间可能从几秒拉到几十分钟甚至更久。达梦在大表 DDL 上的在线能力有限,不像某些数据库有专门的在线变更机制,所以生产环境的大表改造,基本都要靠维护窗口来兜底。

有个细节值得注意:DDL 执行过程中如果因为数据不兼容而失败,达梦一般会回滚这次 DDL 本身,但已经提交的事务不会回来。所以正确的顺序是“先备份、再体检、最后执行”,而不是“先执行、出错再说”。我在早期项目里吃过一次亏,表不大,几十万行,改到一半因为一条脏数据报错,虽然结构没变,但前面攒的一批业务事务被隐式提交了,跟预期的事务边界对不上,排查了半天才定位到是 DDL 引起的。从那以后,凡是涉及结构变更,我都先把会话里的事务处理好再动手。

1.3 先分清三类改动,别一上来就敲 MODIFY

同样是“改字段类型”,风险等级差别很大,我习惯分成三类来处理。

第一类是放宽型,比如VARCHAR(20)扩到VARCHAR(200)、INT扩到BIGINT、NUMBER(10,2)扩到NUMBER(16,4)。这类改动不改动已有数据的语义,理论上不会丢数据,主要风险在锁时长和依赖对象上。只要长度或精度没有触及页大小、索引键长度的天花板,通常可以直接MODIFY。

第二类是收窄型,比如缩短长度、减少小数位、从大类型换小类型。这类必须先做数据体检,把超限数据找出来处理掉,否则 DDL 一定失败。收窄型操作我基本不用直接MODIFY,宁可走临时列迁移,把校验、转换、比对拆成可控的几步。

第三类是跨类型族,比如字符串转数字、字符串转日期、DATE转TIMESTAMP、普通表转大字段。这类改动最容易出问题,因为涉及隐式转换规则,不同数据内容的表现千差万别。稳妥做法是新增临时列、分批转换、逐批校验,确认无误后再替换原列。

把改动先归到这三类里,心里对风险和步骤就有数了。下面先把语法和边界讲清楚,再进入体检和实操。

2. 语法写对只是起点

2.1 MODIFY 是主线,别把别的库的习惯带进来

达梦数据库修改列定义走的是 Oracle 风格,主语法是:

ALTER TABLE 表名 MODIFY 列名 新数据类型 [DEFAULT 默认值] [NOT NULL | NULL];

举个具体例子,把用户表的备注字段从 20 字节扩到 200:

ALTER TABLE t_user MODIFY remark VARCHAR(200);

如果是改精度:

ALTER TABLE t_order MODIFY amount NUMBER(16,4);

顺便把默认值一起调整也可以:

ALTER TABLE t_user MODIFY status INT DEFAULT 0 NOT NULL;

这里要重点提醒一句:不少从 MySQL 或 PostgreSQL 转过来的朋友,习惯写ALTER TABLE t_user ALTER COLUMN remark TYPE VARCHAR(200),在达梦上大概率会吃到语法错误。达梦的ALTER COLUMN子句主要用于处理默认值和空值约束,比如ALTER TABLE t_user ALTER COLUMN status SET DEFAULT 0、ALTER TABLE t_user ALTER COLUMN status DROP NOT NULL,类型变更还是得走MODIFY。具体某个小版本支持到什么程度,建议用disql连上去,在测试库上先敲一遍验证,别直接在生产上试。

改列名是另一条语法:

ALTER TABLE t_user RENAME COLUMN old_name TO new_name;

这条不属于“改类型”,但在临时列迁移方案里会用到,先记在这里。

2.2 一次改一列,还是多列一起改

部分版本支持在一条MODIFY里用逗号分隔改多列,写起来确实省事:

ALTER TABLE t_user MODIFY remark VARCHAR(200), status INT DEFAULT 0;

但我在生产环境基本不用这种写法,原因很实际:一旦其中某一列因为数据不兼容失败,整条语句回滚,你很难快速判断是哪一列出的问题。尤其是表上有几十个字段、改动涉及三四列的时候,一次一列改,报错信息指向明确,排查成本低得多。

还有一个考虑是锁的持续时间。多列合并成一条语句,理论上只加一次锁,听起来更优;但实际操作中,单列失败重试的代价远低于整条语句失败重来的代价。我的取舍是:测试环境可以图省事合并写,生产环境拆开逐条执行,每条执行完立刻做一次数据抽检。

注意:DDL 会隐式提交当前会话的事务。执行前先把业务事务处理干净,避免出现“结构改完了,前面的业务数据却被动提交”的尴尬局面。

2.3 VARCHAR 的长度到底是字节还是字符

这是达梦上最容易被忽略、也最容易造成误判的一个点。VARCHAR(20)里的 20,到底是 20 个字节还是 20 个字符,取决于建库时的初始化参数LENGTH_IN_CHAR。这个参数在dminit初始化实例时确定,取值为 1 表示按字符计算,取值为 0 表示按字节计算。它属于建库参数,事后不能通过改配置文件直接生效,要调整只能重新初始化实例。

为什么这件事重要?假设实例是字节模式,字符集为 UTF-8,一个汉字通常占 3 个字节,那么VARCHAR(20)实际只能存 6 个汉字多一点。业务方说“这个字段要存 50 个字”,你直接把 20 改成 50,结果还是不够用。反过来,如果实例是字符模式,VARCHAR(50)就是实打实的 50 个字符,按字节估算反而会高估需求。

判断当前实例是哪种模式,可以查参数:

SELECT PARA_NAME, PARA_VALUE FROM V$DM_INI WHERE PARA_NAME = 'LENGTH_IN_CHAR';

改类型时对应的换算也要跟着走。字节模式下,业务说“要存 100 个汉字”,按 UTF-8 估算至少需要 300 字节,考虑不同字符集和特殊字符,留点余量给到VARCHAR(400)更稳。字符模式下直接给VARCHAR(100)就够,稍微留点余量给到 120 也行。拿不准字符集影响的话,用LENGTHB和LENGTH分别量一下现有数据的实际占用:

SELECT MAX(LENGTHB(remark)) AS max_bytes, MAX(LENGTH(remark)) AS max_chars FROM t_user;

两个值一比,当前实例按什么单位计算就一目了然了。这个习惯我保持了很长时间,比翻文档快。

2.4 行长度与索引键长度的天花板

达梦的行存储受页大小约束,单条记录不能无限膨胀。页大小在初始化时确定,常见的有 8K、16K、32K,单行记录长度大致不能超过页大小的一半。这意味着如果你把好几个VARCHAR字段一起扩到几千字节,可能触发行超长的问题,表现为插入或更新时报记录长度相关的错误。

索引键长度同样有上限,具体数值跟页大小和版本相关。如果被修改的列上建有索引,把它从短字符串扩到长字符串之前,必须确认新的长度还在索引键允许范围内。超出上限时,要么索引建不上,要么已有的索引在后续维护中出问题。

这两条限制我没法给出一个放之四海皆准的数字,因为不同页大小、不同版本的行为有差异。稳妥做法是:改动前先确认目标库的页大小,翻一下对应版本的官方文档里关于记录长度和索引键长度的说明;更直接的办法是在测试库上建一张结构相同的空表,把目标类型套上去试一次,能不能建、能不能插一条长数据,一试便知。空表试验成本极低,比在生产上撞墙划算得多。

3. 动手前的对象体检

3.1 把数据本身摸清楚

结构变更的第一手依据永远来自数据。以字符串列为例,我通常会跑一组统计:

SELECT COUNT(*) AS total_rows, SUM(CASE WHEN remark IS NULL THEN 1 ELSE 0 END) AS null_rows, MAX(LENGTHB(remark)) AS max_bytes, MAX(LENGTH(remark)) AS max_chars FROM t_user;

这三个指标分别回答:表有多大、空值占比多少、现有数据最长到什么程度。表大意味着锁的时间长,空值多意味着改NOT NULL要谨慎,最大长度直接决定收窄型改动能不能做。

如果是数值列转整数,得先看有没有小数:

SELECT COUNT(*) FROM t_order WHERE amount <> TRUNC(amount);

结果大于零,说明直接改成整数类型会丢精度,要么业务确认可以截断,要么先跟业务方对齐处理规则。

如果是字符串转数字,得先把非数字数据捞出来:

SELECT COUNT(*) FROM t_user WHERE flag IS NOT NULL AND NOT REGEXP_LIKE(flag, '^-?[0-9]+$');

达梦支持REGEXP_LIKE,这条能快速筛出脏数据。数字转换的坑特别多,前后空格、正负号、小数点、科学计数法、空字符串,每一种都可能在转换时报错。与其等 DDL 报错再回头找,不如提前用这条 SQL 把问题数据列出来,逐条跟业务确认处理方式。

字符串转日期也是同理,先用TO_DATE试着转一批,能转过去再谈改结构。

3.2 依赖对象怎么查:索引、约束、视图、程序单元

数据摸清楚了,接下来查这个列被哪些对象引用。达梦提供了一套 Oracle 兼容的数据字典视图,用起来很顺手。

查列的基本定义:

SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE, NULLABLE, DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME = 'T_USER';

查索引:

SELECT i.INDEX_NAME, i.UNIQUENESS, c.COLUMN_NAME, c.COLUMN_POSITION FROM USER_INDEXES i JOIN USER_IND_COLUMNS c ON i.INDEX_NAME = c.INDEX_NAME WHERE i.TABLE_NAME = 'T_USER' ORDER BY i.INDEX_NAME, c.COLUMN_POSITION;

查约束:

SELECT c.CONSTRAINT_NAME, c.CONSTRAINT_TYPE, cc.COLUMN_NAME FROM USER_CONSTRAINTS c JOIN USER_CONS_COLUMNS cc ON c.CONSTRAINT_NAME = cc.CONSTRAINT_NAME WHERE c.TABLE_NAME = 'T_USER';

查依赖这个表的对象:

SELECT OWNER, NAME, TYPE, REFERENCED_NAME, REFERENCED_TYPE FROM USER_DEPENDENCIES WHERE REFERENCED_NAME = 'T_USER';

查视图定义,确认有没有引用目标列:

SELECT VIEW_NAME, TEXT FROM USER_VIEWS WHERE TEXT LIKE '%REMARK%';

查程序单元的编译状态:

SELECT OBJECT_NAME, OBJECT_TYPE, STATUS FROM USER_OBJECTS WHERE OBJECT_TYPE IN ('PROCEDURE','FUNCTION','TRIGGER','PACKAGE') ORDER BY OBJECT_TYPE, OBJECT_NAME;

如果是分区表,还要看分区定义:

SELECT TABLE_NAME, PARTITION_NAME, HIGH_VALUE FROM USER_TAB_PARTITIONS WHERE TABLE_NAME = 'T_ORDER' ORDER BY PARTITION_POSITION;

这一圈查下来,心里就有了一张影响面地图。索引和约束多的列,改完要重建的东西就多;被视图和存储过程引用的列,改完要做失效检查和重新编译;分区表的列类型变更,要确认所有分区的定义是否一致。漏查任何一项,都可能在改完之后才发现某个报表视图打不开,那时候再回滚就麻烦了。

3.3 备份与回滚预案

DDL 隐式提交意味着没有事务级回滚,所以备份是唯一的安全网。达梦常用的备份方式有两类:逻辑导出和物理备份。

逻辑导出用dexp,适合表级或模式级的快速备份:

dexp USERID=SYSDBA/口令@127.0.0.1:5236 \ FILE=t_user_before.dmp \ DIRECTORY=/dm8/backup \ LOG=t_user_before.log \ TABLES=T_USER

导出完成后别只看命令有没有报错,要打开日志确认表数量、行数对得上。我见过导出日志里写着成功、实际表被跳过的情况,原因是权限或表名大小写不匹配,不核对行数根本发现不了。

物理备份用dmrman,适合整库级别的兜底,适合变更范围大、涉及多张表的场景。日常单表改造,逻辑导出加一份建表语句就够用了。

建表语句也要单独留一份,用来对比改前改后的结构差异:

SELECT DBMS_METADATA.GET_DDL('TABLE', 'T_USER') FROM DUAL;

这条在达梦上是否可用取决于版本和兼容包,不可用的话用管理工具右键生成建表语句也行。目的是留一份结构快照,改完之后做 diff,确认只动了预期的那一列。

回滚预案要写清楚三件事:出问题用什么恢复、恢复需要多久、恢复期间业务怎么降级。备份文件放在哪里、谁能取到、恢复命令是什么,这些在动手前就该确认好,别等出事再临时找。

4. 四类典型改造的完整实操

4.1 加长 VARCHAR:最舒服的一类

加长是最常见也最安全的一类。假设t_user.remark原来是VARCHAR(20),业务反馈存不下,要扩到VARCHAR(200),实例是字节模式,UTF-8 字符集。

第一步,体检:

SELECT COUNT(*) AS total_rows, MAX(LENGTHB(remark)) AS max_bytes FROM t_user;

假设结果是 80 万行、最大 18 字节,说明现有数据都在 20 字节以内,扩到 200 字节不会触发行长度问题。

第二步,查依赖:

SELECT INDEX_NAME FROM USER_IND_COLUMNS WHERE TABLE_NAME = 'T_USER' AND COLUMN_NAME = 'REMARK';

如果返回空,说明这列没有索引,直接改。如果有索引,先按 2.4 节的方法确认新长度没超索引键上限。

第三步,执行:

ALTER TABLE t_user MODIFY remark VARCHAR(200);

第四步,验证:

SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH FROM USER_TAB_COLUMNS WHERE TABLE_NAME = 'T_USER' AND COLUMN_NAME = 'REMARK';

再插入一条 150 字节的测试数据,确认能写进去:

INSERT INTO t_user(id, remark) VALUES(-1, RPAD('测', 150, '试'));

验证完把这行删掉。

整个流程下来,小表几秒钟,几十万行的表通常在可接受范围内。真正需要注意的是执行时机,避开业务高峰,提前看一眼有没有长事务占着这张表:

SELECT SESS_ID, SQL_TEXT, STATE FROM V$SESSIONS WHERE STATE = 'ACTIVE';

有活跃事务在跑,先等它结束或者协调暂停,别硬上。

4.2 收窄长度:先治数据,再动结构

收窄的风险高一个量级,因为超限数据会让 DDL 直接失败。假设要把remark从VARCHAR(200)收回到VARCHAR(50)。

第一步,统计超限数据:

SELECT COUNT(*) FROM t_user WHERE LENGTHB(remark) > 50;

第二步,把超限记录捞出来看:

SELECT id, LENGTHB(remark) AS len, SUBSTR(remark, 1, 80) AS preview FROM t_user WHERE LENGTHB(remark) > 50 ORDER BY len DESC;

这批数据怎么处理,得跟业务方一起定。常见几种方式:截断保留前 50 字节、整条置空并记入日志、迁移到另一张扩展表、人工确认后修正。不管选哪种,都要留痕,方便事后追溯。

第三步,处理完再复检一次,确认超限数据为零。然后执行:

ALTER TABLE t_user MODIFY remark VARCHAR(50);

这里有个细节:如果这一列上有索引,缩短长度通常不会让索引失效,但索引占用的空间不会自动收缩,需要重建索引才能释放。达梦上可以用SP_REBUILD_INDEX或者直接DROP后重建:

ALTER INDEX IDX_T_USER_REMARK REBUILD;

具体语法和可用性按版本核对,重建前确认索引定义已经留档。

第四步,验证方式跟加长一样,查数据字典加插测试数据。收窄类操作我还会额外做一次全表校验,确认没有残留的超长数据:

SELECT COUNT(*) FROM t_user WHERE LENGTHB(remark) > 50;

这条必须返回 0 才算收工。

4.3 跨类型族改造:临时列迁移法

跨类型族是最容易翻车的一类,比如把VARCHAR的flag列改成INT,或者把DATE改成TIMESTAMP再改回来。直接MODIFY依赖隐式转换规则,脏数据一多就报错,而且报错信息往往只告诉你哪一行有问题,不告诉你一共有多少行有问题。我基本都走临时列迁移。

以t_user.flag从VARCHAR(10)改到INT为例。

第一步,新增临时列:

ALTER TABLE t_user ADD COLUMN flag_tmp INT;

第二步,清洗数据。把能转的转过去,不能转的先置空并记录:

UPDATE t_user SET flag_tmp = CAST(TRIM(flag) AS INT) WHERE flag IS NOT NULL AND REGEXP_LIKE(TRIM(flag), '^-?[0-9]+$');

达梦的CAST在转换失败时会报错,所以WHERE条件必须先把非数字数据排除掉。这一步建议分批执行,每批一万行左右提交一次,避免 UNDO 空间暴涨:

UPDATE t_user SET flag_tmp = CAST(TRIM(flag) AS INT) WHERE id BETWEEN 1 AND 10000 AND flag IS NOT NULL AND REGEXP_LIKE(TRIM(flag), '^-?[0-9]+$'); COMMIT;

按主键区间一批批推进,比一条大 UPDATE 稳得多。大事务在达梦上会占用大量回滚段,一旦中途失败,回滚时间可能比正向执行还长。

第三步,比对结果,把没转过去的数据捞出来:

SELECT id, flag FROM t_user WHERE flag IS NOT NULL AND flag_tmp IS NULL;

这批就是脏数据,逐条确认处理方式。是空格、是N/A、是空字符串,处理策略不同。

第四步,替换原列。先处理依赖,把原列上的索引、约束、默认值、注释都记录下来,然后:

ALTER TABLE t_user DROP COLUMN flag; ALTER TABLE t_user RENAME COLUMN flag_tmp TO flag;

第五步,重建索引、约束、默认值和注释:

ALTER TABLE t_user MODIFY flag INT DEFAULT 0; COMMENT ON COLUMN t_user.flag IS '状态标记'; CREATE INDEX IDX_T_USER_FLAG ON t_user(flag);

第六步,处理失效对象。视图和存储过程如果引用了原列,改完之后大概率处于失效状态,需要重新编译或重建:

SELECT OBJECT_NAME, OBJECT_TYPE, STATUS FROM USER_OBJECTS WHERE STATUS = 'INVALID';

查出来的逐个处理。视图一般重建,存储过程用ALTER ... COMPILE或者直接重新执行一遍创建语句。

临时列迁移法看起来步骤多,但每一步都可控、可验证、可暂停。相比一条MODIFY撞运气,我更愿意多花半小时把过程拆开。

4.4 大表与分区表:把窗口期算清楚

表到了千万行级别,改类型的耗时就不能忽略了。达梦在大表 DDL 上基本是重建数据的方式,耗时跟表大小、磁盘吞吐、是否有索引需要重建都有关系。粗估方法是看表的物理占用:

SELECT TABLE_NAME, BYTES/1024/1024 AS size_mb FROM USER_SEGMENTS WHERE SEGMENT_NAME = 'T_ORDER';

假设表占 20GB,磁盘顺序写吞吐按 200MB/s 算,光数据重写就要 100 秒左右,加上索引重建、日志写入、锁等待,实际耗时翻两三倍很常见。这个估算很粗,但足够用来判断是“午休时间搞定”还是“必须排夜间窗口”。

分区表还有额外约束。列类型变更要保证所有分区的定义一致,如果表是按范围分区、列本身又是分区键,改动限制会更多。操作前把所有分区的定义查一遍,确认没有历史遗留的不一致:

SELECT PARTITION_NAME, COLUMN_NAME, DATA_TYPE, DATA_LENGTH FROM USER_PART_COL_STATISTICS WHERE TABLE_NAME = 'T_ORDER';

具体视图名和字段按版本核对,不同版本的数据字典略有差异。

大表改造还有一个实用技巧:先在测试环境用同等数据量的表跑一遍,记录耗时和锁等待情况,再按比例推算生产耗时。测试环境的硬件配置跟生产往往差很多,所以推算时要留足余量,我给的经验系数是乘以 2 到 3。

窗口期确定之后,执行顺序也要安排好:先停相关批量任务,再确认没有长事务,然后执行 DDL,最后做验证和依赖对象修复。验证通过再恢复业务。整个过程中最好有人在旁边盯着会话状态:

SELECT SESS_ID, STATE, SQL_TEXT FROM V$SESSIONS WHERE STATE = 'ACTIVE';

一旦发现锁等待堆积,及时判断是继续等还是中止。

5. 问题排查与收尾验证

5.1 常见报错与处理对照

改字段类型过程中遇到的报错,大部分集中在数据不兼容和对象依赖两类。下面这张表是我这些年攒下来的高频场景,报错信息的原文会因版本不同略有差异,但排查方向是一致的。

现象大概率原因处理方向
提示数据长度超出定义收窄长度时存在超限数据先查LENGTHB超限行,清洗后再改
提示数据类型转换失败字符串转数字/日期时存在脏数据用正则筛出非目标格式数据,逐条处理
提示无法修改列,存在依赖索引、约束、视图正在引用该列先查依赖字典,评估是否需先删后建
提示记录长度超限单行总长度超过页大小限制拆分字段、改用大字段类型,或调整页大小
DDL 长时间不返回表上有长事务或锁等待查V$SESSIONS、V$LOCK,协调释放
改完视图打不开视图依赖的列类型变了,视图失效查USER_OBJECTS状态,重建视图
改完存储过程报错程序单元编译失效重新编译或重新执行创建语句
默认值丢失MODIFY未带DEFAULT子句改完后单独补DEFAULT定义

查锁的语句可以这样写:

SELECT * FROM V$LOCK; SELECT SESS_ID, STATE, SQL_TEXT FROM V$SESSIONS WHERE STATE = 'ACTIVE';

达梦的V$LOCK视图字段跟版本有关,具体列名以实际查询结果为准。核心思路是找到持锁会话,判断它是在正常跑业务还是卡住了,再决定等待还是协调。

还有一类容易被忽略的问题:改完之后统计信息过期,执行计划突然变差。DDL 之后建议手动收集一次统计信息:

CALL DBMS_STATS.GATHER_TABLE_STATS('模式名', 'T_USER');

这条在达梦上是否可用取决于是否安装了兼容包,不可用的话用管理工具里的统计信息更新功能也行。目的是让优化器拿到新的列长度和分布,避免因为旧统计信息选错执行计划。

5.2 改完之后的五项验证

改完不验证,等于没改。我固定会做五项检查。

第一项,结构核对:

SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH, DATA_PRECISION, DATA_SCALE, NULLABLE, DATA_DEFAULT FROM USER_TAB_COLUMNS WHERE TABLE_NAME = 'T_USER' AND COLUMN_NAME = 'REMARK';

第二项,数据抽检。随机取一批行,确认值还在、格式正常:

SELECT id, remark FROM t_user WHERE id IN (SELECT id FROM t_user WHERE ROWNUM <= 20);

第三项,写入测试。插一条符合新类型边界的数据,确认能写能读,测完删掉。这一步能验证默认值、空值约束、索引是否都正常。

第四项,依赖对象检查:

SELECT OBJECT_NAME, OBJECT_TYPE, STATUS FROM USER_OBJECTS WHERE STATUS <> 'VALID';

第五项,业务侧验证。挑一两个引用这张表的接口或报表,实际跑一遍。数据库层面一切正常,不代表应用层没问题,比如 ORM 映射里写死了字段长度,改完照样报错。

这五项做完,我才认为这次变更是完成的。

5.3 几条踩过坑才明白的经验

第一条,改类型之前先把这条 SQL 在测试库完整跑一遍,包括备份、执行、验证、回滚的完整流程。回滚演练比正向执行更重要,因为出事的时候才是真正需要它的时候。

第二条,收窄型改动永远不要相信“应该没有超长数据”。我见过太多“数据很干净”的自信判断,最后被一条三年前的测试数据打脸。跑一遍MAX和COUNT,几秒钟的事。

第三条,临时列迁移法里,DROP COLUMN之前一定要确认新列的数据已经比对通过。我踩过一次坑,转换逻辑写错了一个边界条件,原列删掉之后才发现新列少了一批数据,只能从备份恢复。从那以后,我固定会在删列前跑一次双列比对:

SELECT COUNT(*) FROM t_user WHERE (flag IS NULL AND flag_tmp IS NOT NULL) OR (flag IS NOT NULL AND flag_tmp IS NULL);

这条必须返回 0。

第四条,字节模式下给字符串字段定长度,一定要按字节算,别按字符数拍脑袋。业务说“存 100 个字”,你给VARCHAR(100),UTF-8 下只能存 33 个汉字,上线第二天就得返工。

第五条,DDL 执行时间安排在业务低峰,并且提前通知相关方。改类型这件事,数据库层面可能几十秒就完事,但应用层的连接池、缓存、报表任务可能需要额外时间恢复,留出缓冲比抢那几分钟更重要。

改动本身不复杂,复杂的是改动之前那些看不见的依赖和约束。把体检做足、把方案拆细、把回滚备好,剩下的就是按部就班执行。我在实际项目里越来越倾向于“慢一点、稳一点”,尤其是数据库结构变更,快往往不是优点。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询