简介:这是一套基于ThinkPHP5.0框架与FastAdmin平台打造的达梦数据库DM集成设计源码,面向需要将国产数据库落地到业务系统的PHP开发者,核心解决TP5框架、FastAdmin后台与达梦数据库之间的连接驱动、SQL语法兼容及数据访问封装问题,适合政务、企业数字化转型及国产化替代场景。压缩包共21个文件,包含19个PHP文件、1个TXT配置说明和1个SQL数据库脚本,整体体积仅214KB,便于携带和快速部署。PHP文件覆盖连接器、查询构造器、异常处理等关键模块,TXT文件提供部署与配置要点,SQL脚本则给出基础库表结构,便于对照理解整条集成链路。已有606人浏览学习,代码结构紧凑、分层清晰,开发者可直接参考其中的驱动适配与查询封装思路,迁移至自有项目,也可作为FastAdmin对接达梦数据库的起步模板,显著缩短集成开发周期。
1. 从 MySQL 到达梦 DM,FastAdmin 迁移到底难在哪:驱动、语法、主键三座山
某个要过信创验收的项目,后台管理系统用的是 FastAdmin 这套基于 ThinkPHP5.0 的快速开发框架,数据库被指定成达梦 DM8。最直观的做法是改一下application/database.php里的 type 字段,但第一次启动就会直接报Class not found——ThinkPHP5.0 内置的数据库连接器里根本没有达梦这一项;等把驱动和连接器补齐,建表脚本又会在ENGINE、AUTO_INCREMENT这些 MySQL 方言上卡住;表建起来之后,后台列表第一页正常,第二页分页翻车,角色管理的权限字段又报FIND_IN_SET函数不存在。这套“基于ThinkPHP5.0的fastadmin达梦数据库DM集成设计源码”的改造,问题集中在三处:PHP 驱动与 TP 连接层、MySQL 建表与查询语法的批量改写、达梦的自增主键与内置函数差异。下面按顺序拆开讲,每一步都是可以直接复现的最小改动。
2. 达梦连接层适配:选对 PHP 驱动,再给 ThinkPHP5.0 扩展一个 Dm 连接器
连接层是整个改造的地基。达梦官方提供的 PHP 驱动和 MySQL 的不一样,TP5.0 也不认识 dm 这个 type,所以要先在 PHP 层面把达梦的 PDO 驱动接好,再在框架层面补一个连接器和配套的查询构造器。
2.1 达梦 PHP 驱动,首选 pdo_dm 而不是 ODBC
达梦官网针对 PHP 发布的是pdo_dm扩展,FastAdmin 常见运行在 PHP 5.6 到 7.x 上,达梦安装包自带的drivers目录里一般有对应版本的 PHP 驱动源码。拿到和当前 PHP 版本匹配的源码包后,按标准 PHP 扩展编译流程装:
# 进入达梦驱动源码目录,路径以你的安装目录为准 cd /opt/dmdbms/drivers/php_pdo phpize ./configure --with-php-config=/usr/local/php/bin/php-config make && make install # 然后在 php.ini 中追加 extension=pdo_dm.so # 验证驱动是否加载成功 php -m | grep -i pdo_dm # 验证能否真实连上达梦,默认端口 5236 php -r "new PDO('dm:host=127.0.0.1;port=5236','SYSDBA','SYSDBA'); echo 'ok';"最后一行new PDO如果输出 ok,说明 PHP 到达梦的通道已经通了。这里有个常见的坑:phpize 编译时--with-php-config指向的 PHP 版本必须和 Web 环境用的是同一个,否则模块加载后php -m能看见,FastAdmin 却报“驱动不存在”。
如果实在找不到和 PHP 版本匹配的pdo_dm,退路是走 ODBC:达梦提供 ODBC 驱动,PHP 端用pdo_odbc连接。这个方案能跑通,但性能比pdo_dm差,而且查字段类型、拿自增 ID 时行为差异更大。我的建议是优先把pdo_dm搞定,ODBC 只作为临时应急。顺带提醒一句:有人习惯拿 Navicat 连达梦,注意 Navicat 15 以上版本才自带 DM 驱动,老版本连接时报的错和 PHP 这边是同一类问题,别被误导。
2.2 复制 Mysql 连接器改成 Dm:四个必须动的位置
ThinkPHP5.0 的数据库连接器在thinkphp/library/think/db/connector/目录下,内置了 Mysql、Pgsql、Sqlsrv、Sqlite 等。达梦的 SQL 方言同时兼容 MySQL 和 Oracle 的一些习惯,所以我一般直接复制Mysql.php改成Dm.php,在子类里覆盖几个关键方法,而不是从零实现。
第一个是parseDsn,达梦 PDO 的 DSN 只需要 host 和 port,模式和库名都在登录后决定;第二个是getTables,达梦查表清单要用ALL_TABLES系统视图;第三个是getFields,字段信息来自ALL_TAB_COLUMNS。核心代码长这样:
<?php namespace think\db\connector; use think\db\Connection; class Dm extends \think\db\connector\Mysql { // 1. 达梦 PDO 的 DSN 与 MySQL 不同,不需要 dbname protected function parseDsn($config) { $dsn = 'dm:host=' . $config['hostname'] . ';port=' . $config['hostport']; return $dsn; } // 2. 列出当前模式下所有表 public function getTables($dbName = '') { $owner = strtoupper($this->config['username']); $result = $this->query("SELECT TABLE_NAME FROM ALL_TABLES WHERE OWNER='" . $owner . "'"); $tables = []; foreach ($result as $row) { $tables[] = strtolower($row['TABLE_NAME']); } return $tables; } // 3. 读取字段信息,FastAdmin 的 CRUD 生成依赖这个返回结构 public function getFields($tableName) { $tableName = strtoupper($tableName); $result = $this->query( "SELECT COLUMN_NAME, DATA_TYPE, DATA_LENGTH, NULLABLE " . "FROM ALL_TAB_COLUMNS WHERE TABLE_NAME='" . $tableName . "' ORDER BY COLUMN_ID" ); $info = []; foreach ($result as $row) { $name = $row['COLUMN_NAME']; $info[$name] = [ 'name' => $name, 'type' => $row['DATA_TYPE'], 'notnull' => ($row['NULLABLE'] === 'N'), 'default' => '', 'primary' => ($name === 'ID' ? '1' : ''), 'autoinc' => '', ]; } return $info; } }这里有一个取舍:getFields里我把名为ID的字段直接当成主键,没有真的去 joinALL_CONSTRAINTS查约束。FastAdmin 所有核心表的主键恰好都叫id,这个简化规则在这个项目里够用。如果你遇到的表的业务主键不叫id,还是老老实实去查系统约束表,否则编辑页会找不到主键条件。
另外,TP5.0 会按 type 映射查询构造器类,只加连接器还不够。thinkphp/library/think/db/builder/目录下要补一个Dm.php,继承 Mysql 的构造器,这个文件在分页改写那一节会用到。连不上时第一优先排查这两个文件是否都在正确的位置。
2.3 database.php 的配置与自动加载
连接器和构造器就位后,application/database.php里把 type 改成 dm,DSN 相关的配置如下:
return [ 'type' => 'dm', 'hostname' => '127.0.0.1', 'database' => 'FASTADMIN', 'username' => 'SYSDBA', 'password' => 'SYSDBA', 'hostport' => '5236', 'prefix' => 'think_', 'charset' => 'utf8', 'debug' => false, 'params' => [ \PDO::ATTR_CASE => \PDO::CASE_NATURAL, ], ];先说 type=dm:TP5.0 的数据库连接类会自动去找think\db\connector\Dm,找不到就报Class not found。我把Dm.php放在thinkphp/library/think/db/connector/下,命名空间是think\db\connector,这样框架的类自动加载能直接命中。如果你不想改框架目录,放在extend目录也可以,但要手动注册对应的命名空间前缀,我嫌麻烦,没走那条路。
再说database这个参数:在 MySQL 里它是库名,在达梦语境下它是模式名,也就是 Schema,不是传统意义的数据库。连接账号 SYSDBA 登录后默认模式就是 SYSDBA,如果建表时表建在别的模式下,这里就要写那个模式名。这是后面几乎所有“查不到表”报错的源头。
2.4 达梦模式(Schema)与 FastAdmin 表前缀
达梦把“用户”和“模式”绑在一起,新建一个用户FASTADMIN时会自动生成同名的FASTADMIN模式,表默认建在这个模式下。FastAdmin 的表名前缀是think_,比如think_user,在达梦默认大小写规则下会保存成THINK_USER。
如果项目里已经用 SYSDBA 建好了表,而database配置写的是 FASTADMIN,查询时就会去 FASTADMIN 模式里找表,自然找不到。一个省事的处理方式是在连接建立后强制切换模式,我一般会在连接器里加一个 connect 方法:
protected function connect() { $pdo = parent::connect(); $pdo->exec('SET SCHEMA ' . $this->config['database']); return $pdo; }这样database配置写什么模式,连接后就把会话切到什么模式。注意这行代码只在连接用户和模式名不一致时需要,如果连接用户就是 FASTADMIN 且表都在 FASTADMIN 模式下,不需要执行也能查得到。生产环境我不建议用 SYSDBA 跑业务,正确做法是新建一个和模式同名的业务用户,把表赋权给它,连接串里直接写业务用户。
3. 把 MySQL 建表脚本迁到达梦 DM:批量替换规则与数据导入
连接层跑通只是开始。FastAdmin 自带的install.sql是标准的 MySQL 方言,里面ENGINE=InnoDB、AUTO_INCREMENT、反引号、COMMENT全都有,直接丢给达梦执行,第一条建表语句就会报语法错误。要先把 DDL 转成达梦能吃的语法,再谈数据导入。
3.1 建表脚本的批量替换规则
我一般用 Python 脚本做一次性文本替换,不建议手工在编辑器里改上百个表。下面这个脚本覆盖了 FastAdmin 安装 SQL 里最常见的六类 MySQL 方言:
#!/usr/bin/env python3 import re def mysql_to_dm(sql: str) -> str: # 去掉表级 ENGINE、CHARSET、COLLATE 后缀 sql = re.sub(r"ENGINE\s*=\s*\w+.*?(?=;|$)", "", sql, flags=re.I) sql = re.sub(r"DEFAULT\s+CHARSET\s*=\s*\w+", "", sql, flags=re.I) sql = re.sub(r"COLLATE\s*=\s*\w+", "", sql, flags=re.I) sql = re.sub(r"\s+COMMENT\s+'[^']*'", "", sql, flags=re.I) # 自增主键转达梦 IDENTITY 写法 sql = re.sub( r"\bint\s*\(\s*\d+\s*\)\s+NOT\s+NULL\s+AUTO_INCREMENT", "INT IDENTITY(1,1) NOT NULL", sql, flags=re.I, ) # 去掉反引号和 MySQL 的 KEY 索引定义行 sql = sql.replace("`", "") sql = re.sub(r"^\s*KEY\s+.*?(,?\s*)$", "", sql, flags=re.I | re.M) # 去掉 USE / CREATE DATABASE 这类实例级语句 sql = re.sub(r"^\s*(USE|CREATE DATABASE)\b.*?;", "", sql, flags=re.I | re.M) sql = re.sub(r"int\s*\(\s*\d+\s*\)\s*unsigned", "INT", sql, flags=re.I) sql = re.sub(r"tinyint\s*\(\s*\d+\s*\)", "SMALLINT", sql, flags=re.I) return sql if __name__ == "__main__": src = open("fastadmin.sql", encoding="utf-8").read() open("fastadmin_dm.sql", "w", encoding="utf-8").write(mysql_to_dm(src))说明一下脚本里几个关键参数的含义。IDENTITY(1,1)是达梦的自增列写法,起始值和步长都是 1,这一步把 MySQL 的AUTO_INCREMENT语义等价替换掉了。COMMENT被我整体删掉是因为达梦建表语句里不认行内注释,注释信息丢失对业务没影响,需要文档时再补。KEY行直接删掉其实会丢索引,FastAdmin 核心表的索引不多,跑起来性能不明显,如果后续发现慢查询,再按实际查询条件手工CREATE INDEX。
这个脚本还有一个边界情况要留意:如果 SQL 里出现ALTER TABLE ... AUTO_INCREMENT=xx之类的语句,达梦也是不支持的,脚本没有覆盖,需要自己再补一条删除规则。转换完先别急着执行,打开转换后的 SQL 文件,随机抽三张表看 DDL 是否完整。
3.2 达梦上 FastAdmin 高频 SQL 的改写对照
建表跑通后,查询语句层面的差异才是隐藏最深的。下面这张表是迁移中真正会遇到的对应关系,不只是教科书式的语法对照:
| MySQL 写法 | 达梦 DM8 写法 | 出现位置 |
|---|---|---|
NOW() | SYSDATE | 模型里写时间戳的地方 |
IFNULL(a, 0) | NVL(a, 0) | 统计查询、列表汇总 |
LIMIT 0, 10 | LIMIT 10 OFFSET 0 | 分页列表 |
FIND_IN_SET('1', rules) | 自定义函数(见第4章) | FastAdmin 角色权限判断 |
GROUP_CONCAT(col) | LISTAGG(col, ',') | 菜单或角色聚合展示 |
UNIX_TIMESTAMP() | (SYSDATE - TO_DATE('1970-01-01','YYYY-MM-DD')) * 86400 | 时间戳转换 |
INSERT ... ON DUPLICATE KEY UPDATE | MERGE INTO ... USING ... | 手动写的幂等插入 |
这些函数不一定全部会碰到。FastAdmin 框架自身的 admin 控制器里,最常见的是FIND_IN_SET和分页的LIMIT写法;而GROUP_CONCAT和UNIX_TIMESTAMP更多出现在你后来二次开发的业务代码里。所以别只看框架,拿grep -rn "NOW()\|IFNULL\|UNIX_TIMESTAMP\|FIND_IN_SET" application/把整个应用目录扫一遍,这个动作能省掉后面一半的运行时 500 报错。
达梦也提供了 MySQL 兼容模式,部分实例在初始化时直接开启后,LIMIT 0,10和反引号都能凑合用。但我建议还是按上表的写法改代码,理由不复杂:兼容模式不是所有达梦版本的默认行为,换新实例或换其他国产数据库时,代码层的适配能直接带走,不用重新踩一遍。
3.3 导入数据:disql 与 DTS 迁移工具
转换后的建表脚本用达梦自带的 disql 工具执行,交互式方式比较稳,报错时能当场看到具体位置:
/opt/dmdbms/bin/disql SYSDBA/SYSDBA@127.0.0.1:5236 # 进入交互界面后执行 SQL> start /data/fastadmin_dm.sql SQL> exitstart后面的路径是服务器本机的绝对路径,不是客户端路径。如果脚本中途报错,disql 默认会继续往下走,日志里能看到第几条 SQL 失败,这个信息比工具返回的“执行失败”四个字有用得多。导入完成后用SELECT COUNT(*) FROM ALL_TABLES WHERE OWNER='FASTADMIN'确认表数量,再和 MySQL 源库核对一遍表名清单。
存量数据的迁移我一般用达梦安装目录tool下的 DTS 数据迁移工具,可视化界面里选 MySQL 作为数据源,达梦作为目标库,字段类型映射基本由工具自动完成。需要手动盯的是字符集:MySQL 源库如果是utf8mb4,导入前确认达梦库的字符集也支持 4 字节字符,否则 emoji 这类字符会被截断。千万不要直接把 MySQL 的 mysqldump 备份文件拿给 disql 执行,格式完全不兼容,光 INSERT 语句的引号转义规则就够折腾一小时。
4. FastAdmin 运行时适配:自增主键、find_in_set 与分页改写
建表结构和连接层都通了,真正的运行时问题才开始暴露。FastAdmin 在 MySQL 上跑得好好的逻辑,换到达梦后最明显的三个差异点是:插入后拿不到自增主键、权限判断的FIND_IN_SET函数不存在、分页第二页直接报错。
4.1 自增主键:序列+触发器的达梦标准姿势
达梦支持IDENTITY自增列,但 FastAdmin 部分业务代码会在插入时显式指定主键(比如数据导入、表关联插入),IDENTITY列遇到显式插入会直接报错。我采用的更稳的做法是序列加触发器,让达梦像 Oracle 一样在插入前自动生成主键:
-- 为 think_user 表建序列 CREATE SEQUENCE SEQ_THINK_USER INCREMENT BY 1 START WITH 1; -- 插入前自动补主键的触发器 CREATE OR REPLACE TRIGGER TRG_THINK_USER BEFORE INSERT ON THINK_USER FOR EACH ROW BEGIN IF :NEW.ID IS NULL OR :NEW.ID = 0 THEN SELECT SEQ_THINK_USER.NEXTVAL INTO :NEW.ID FROM DUAL; END IF; END;序列名和触发器名建议按SEQ_表名、TRG_表名的规则统一命名,后面写批处理建序列时不容易乱。触发器里的IF :NEW.ID IS NULL OR :NEW.ID = 0是一个很关键的判断:FastAdmin 里大部分插入不显式传 id,但个别模块会传 0 或空值,这个条件可以兼容两种情况,不强覆盖显式传入的主键。
FastAdmin 的核心表大概二十来张,全部要建序列和触发器。这个动作没法省,也没必要手动一条条敲,用管理工具的批量执行脚本生成一遍就行。达梦也有SET IDENTITY_INSERT 表名 ON这种开关来允许显式插入,但我试过在部分版本上行为不一致,所以直接放弃,统一走序列方案。
4.2 TP5 查完不回 ID:重写 getLastInsID
建好了序列和触发器,还得让 ThinkPHP5.0 在插入数据后拿到正确的主键返回值。FastAdmin 的增删改查控制器里,$model->save()之后经常要读$model->id用来拼接返回结果,这依赖于 TP 底层的getLastInsID,调用链上对应的是 PDO 的lastInsertId()。pdo_dm在这个方法上表现不稳定,要么返回 0,要么抛异常,所以我在连接器里重写了它:
public function getLastInsID($sequence = null) { // 手动传了序列名时,直接查序列当前值 if ($sequence) { $data = $this->query('SELECT ' . $sequence . '.CURRVAL AS ID FROM DUAL'); return $data[0]['ID']; } try { return (int) parent::getLastInsID(); } catch (\Throwable $e) { // pdo_dm 的 lastInsertId 不给力,落回 0 由调用方兜底 return 0; } }实际调用时,FastAdmin 的模型插入并不会传 sequence,所以parent::getLastInsID()一旦返回 0,业务代码就会拿不到主键。更稳妥的做法是在业务代码里插入后直接查对应序列的.CURRVAL:
$userId = Db::name('user')->where('id', $insertId)->value('id');如果插入时触发了触发器,其实可以直接用Db::name('user')->insert($data, false)的返回值,配合事务回滚来兜底。这个问题是达梦迁移里最容易“看起来成功、实则数据错乱”的点,建议在测试阶段就专门写一个循环插入 100 条的脚本,验证getLastInsID的稳定性。
4.3 给达梦补一个 FIND_IN_SET 兼容函数
FastAdmin 的 auth 权限判断里,角色组的 rules 字段存的是1,2,5这种逗号分隔的菜单 ID 串,MySQL 里用FIND_IN_SET判断。达梦没有这个内置函数,解决办法是在数据库里创建一个同名的兼容函数:
CREATE OR REPLACE FUNCTION FIND_IN_SET( P_SEARCH IN VARCHAR, P_LIST IN VARCHAR ) RETURN INT IS V_LIST VARCHAR(8188); V_ITEM VARCHAR(8188); V_POS INT; BEGIN IF P_SEARCH IS NULL OR P_LIST IS NULL THEN RETURN 0; END IF; V_LIST := P_LIST || ','; LOOP V_POS := INSTR(V_LIST, ','); IF V_POS = 0 THEN EXIT; END IF; V_ITEM := TRIM(SUBSTR(V_LIST, 1, V_POS - 1)); IF V_ITEM = TRIM(P_SEARCH) THEN RETURN 1; END IF; V_LIST := SUBSTR(V_LIST, V_POS + 1); END LOOP; RETURN 0; END;这个函数是完整匹配逻辑,FIND_IN_SET(1, '10,11')会正确返回 0,不会像INSTR那样误判。参数类型必须带长度,达梦里函数参数裸写VARCHAR会报编译错误。函数建好后 FastAdmin 的权限判断代码一行都不用改,达梦默认会把 SQL 里的函数名转成大写去匹配这个大写函数。
注意一个细节:这个函数要建在业务用户所在的模式里,不是建在 SYSDBA 下。否则连接业务用户执行函数时,达梦还会报函数不存在。
4.4 分页与 limit:第二页翻车的常见原因
FastAdmin 列表页第二页翻车是达梦迁移里最有辨识度的故障。TP5.0 的 Mysql 构造器在分页时会把 limit 拼成LIMIT 10,20这种以逗号分隔的写法,达梦在非 MySQL 兼容模式下直接拒绝执行。需要在thinkphp/library/think/db/builder/Dm.php里重写分页解析:
namespace think\db\builder; class Dm extends Mysql { protected function parseLimit($limit) { if (empty($limit)) { return ''; } if (strpos($limit, ',')) { list($offset, $rows) = explode(',', $limit); return ' LIMIT ' . intval($rows) . ' OFFSET ' . intval($offset); } return ' LIMIT ' . intval($limit); } }这段代码把10,20拆成LIMIT 20 OFFSET 10,语义完全一致。两个intval是经验之谈:pdo_dm的参数绑定对 limit 里的字符串类型处理有玄学问题,曾经把LIMIT '10'直接拼到 SQL 里导致达梦报数值格式错误,强转成 int 后这个坑消失了。
框架里凡是手动写->paginate()或->page()的地方都会走这个构造器,重写一次全局生效。如果改完还报分页错误,先看 FastAdmin 的公共控制器里有没有自己拼 SQL 的地方,那种场景只能手动把 limit 写成标准姿势。
5. 达梦适配的常见报错与排查:模式、函数、大小写三类坑
下面几条是迁移达梦时会反复遇到的报错,每一条都按现象、原因、解决的顺序记录,是我自己踩过不止一遍的坑。
5.1 连接成功却查不到表:报“无效的模式名”
现象:disql 里用 SYSDBA 建表一切正常,FastAdmin 后台一打开列表页就报“无效的模式名”或类似ORA-00942: table or view does not exist,但连数据库本身是通的。
原因:达梦的模式名和用户名绑定。SQL 脚本是 SYSDBA 执行的,表挂在 SYSDBA 模式上;而database.php里配置的database是 FASTADMIN,TP 查询时就会去 FASTADMIN 模式里找表,找不到就报无效模式。
解决:二选一。要么把表和连接账号统一到同一个模式,新建一个业务用户并把表赋权给它;要么在连接器的 connect 方法里执行SET SCHEMA SYSDBA把会话切过去。我推荐前者,生产环境不要用 SYSDBA 跑业务,权限太粗,出了问题也不好定位。
5.2 改完 type 报 Class not found
现象:application/database.php里 type 改成 dm 后,任何数据库操作直接抛Class 'think\db\connector\Dm' not found。
原因:TP5.0 的数据库连接类是按think\db\connector\加 type 首字母大写来反射加载的,框架自带连接器里没有 Dm,文件没放对位置就找不到类。另一个容易被忽略的是查询构造器think\db\builder\Dm也不存在,同样会报类找不到。
解决:把连接器放在thinkphp/library/think/db/connector/Dm.php,构造器放在thinkphp/library/think/db/builder/Dm.php,namespace 分别对应think\db\connector和think\db\builder。改完清一下 runtime 缓存,TP5.0 的类映射缓存有时会让新文件不生效。
5.3 where 条件里的小写列名查不到:报“无效的列名”
现象:后台列表能打开,但带条件的查询返回空或直接报“无效的列名”,看日志 SQL 是WHERE status = 'normal',而实际的列名是STATUS大写。
原因:达梦默认会把不带引号的标识符转成大写,FastAdmin 迁移前的代码和建表脚本都是小写风格,建表时如果用了反引号或引号强制保留了小写,列名就变成小写,查询时大写STATUS对不上。
解决:建表脚本统一用大写标识符,代码层的查询由达梦自动转换成大写就能匹配上。如果你的建表脚本保留了小写列名,就需要把所有涉及该表的查询都加双引号,这个工作量很大,所以迁移建表时一定要先统一风格。开启实例的 MySQL 兼容模式也能缓解一部分大小写问题,但我依然建议以代码层适配为主。
5.4 导入数据时报“无效的数值”或“字符串截断”
现象:DTS 迁移或者 disql 导入 MySQL 存量数据时,某张表报“无效的数值”,停下后核对发现是 datetime 字段里有0000-00-00 00:00:00;还有的是 varchar 字段报“字符串截断”。
原因:MySQL 的 datetime 允许零值日期,达梦的日期类型不允许;MySQL 的 varchar 在非严格模式下对超长值只是截断加警告,达梦直接报错。
解决:导入前写个脚本把 MySQL 表里的0000-00-00全部替换成1970-01-01 00:00:00或 NULL;超长文本先按字段查SELECT MAX(LENGTH(字段)),逐个超出长度上限的列决定是加大字段长度还是业务上截断。这一步在正式导入前做,不要在导入报错后再回头改源表。
5.5 后台菜单报“函数不存在”:FIND_IN_SET 与 NOW()
现象:FastAdmin 后台登录成功后,菜单管理或角色管理页面直接 500,日志里报FUNCTION SYSDBA.FIND_IN_SET does not exist,或者类似NOW() 不是可识别的内置函数。
原因:FastAdmin 框架或你二次开发的代码里用了 MySQL 专有函数,达梦没有内置同名的实现。
解决:FIND_IN_SET用第 4.3 的自定义函数补上;NOW()、IFNULL、UNIX_TIMESTAMP这类函数按第 3.2 的对照表全局替换。排查时打开application目录递归搜索所有函数名,一条条过,不要凭感觉认为某个模块不会用到。这个步骤是整个迁移里最花时间的,但也是最有价值的,搜完一遍之后对项目的 SQL 使用情况会有完整认知。
6. 验证链路与最小改造清单:从本机到信创环境的收尾检查
这套方案完整跑通后,先别急着上生产,按下面的顺序过一遍,能排查掉九成以上遗留问题。
最小改造清单其实是四样东西:扩展后的connector/Dm.php连接器、builder/Dm.php构造器、转换后的建表脚本、以及序列触发器和FIND_IN_SET函数。数据迁移只涉及一次导入工具操作,代码层面不需要为达梦写任何业务分支,这就是这套方案的核心价值。
验证时先清缓存再跑命令:
# 清 ThinkPHP 运行时缓存,避免旧表结构定义残留 rm -rf runtime/cache runtime/temp # 用命令行通道跑一次定时任务,确认 CLI 环境下连接器正常 php think crontab然后打开后台,把用户管理、角色组、菜单管理、系统配置、操作日志五个模块逐个做一遍新增、编辑、删除、列表分页操作。重点看两处:编辑页能不能正确回显主键,删除后跳回列表页的页码是否正常。如果达梦实例开了服务端日志,操作完看日志目录里的 SQL 执行记录,能直接发现哪些语句走了全表扫描或隐式类型转换。
迁移完成后建议做一次行数对账:两张表为一组,分别查SELECT COUNT(*)、单列求和、唯一键去重数,和 MySQL 源库逐一比对。这个动作比随机抽查靠谱得多,尤其是日志表、关联表这类行数大且不显眼的数据。
最后说个习惯:我每次做完达梦迁移,都会把 MySQL 原库完整保留两周,转换脚本和替换规则放进版本库,一旦线上暴露问题随时可以重推。“后悔药”不是没有,而是取决于你迁移前有没有把脚本和原库留好。这种集成改造不怕慢,怕的是自认为“已经兼容了”就跳过后面的对账环节。希望帮到你。
本文还有配套的精品资源,点击获取