最近公司响应信创号召,要求所有内部系统换成国产软硬件环境。我们团队负责的禅道项目管理系统,就这么被推到了风口浪尖上。虽然表面上只是把Windows换成了麒麟、把MySQL换成了达梦,但真做起来,每一个环节都在考验耐心。
一、事情是怎么开始的
公司年初接到集团通知:所有业务系统必须在年底前完成信创适配。所谓信创,就是要把原来基于国外芯片、操作系统、数据库的整套环境,换成国产的。具体到我们这边,服务器要从Intel换成鲲鹏或者飞腾,操作系统从CentOS换成银河麒麟或者统信UOS,数据库呢,老大指定要换成达梦。
我们手上的禅道用了好几年,里面全是项目任务、需求、Bug、测试用例。以前跑在云服务器上,MySQL躺着不用管。现在要求迁移到公司内网的信创机器上,而且数据库必须用达梦。
一看这任务,我心里大概有数,这活不好干。因为禅道本身是基于PHP开发的项目管理系统,默认只面向MySQL和MariaDB做完整支持,对达梦的适配基本没有官方文档。但公司硬性要求,我们也只能硬着头皮上。
二、我们的目标环境长什么样
我们分配到的测试机器是一台鲲鹏920服务器,操作系统是银河麒麟V10(ARM版),数据库是达梦DM8。PHP版本是7.4,禅道版本是开源版12.5.3。还有一台机器装了统信UOS作为备用测试环境。
先看下操作系统:
# 技术栈:Shell / 银河麒麟V10
# 查看系统信息
cat /etc/os-release
# 或者用
uname -a
再看下PHP环境:
# 技术栈:Shell / PHP
# 检查PHP版本和已加载的扩展
php -v
php -m
结果发现默认只有PDO、mysqlnd这些,没有达梦相关的pdo_dm。这就意味着我们得自己折腾扩展。
另外,达梦默认端口是5236,不是3306。初次见面,我还差点用MySQL客户端去连,自然连不上。
三、第一次接触达梦数据库:兼容模式能不能救我们?
达梦数据库其实可以兼容多种数据库的语法。在初始化实例的时候,可以通过参数设置兼容模式。我们选择了兼容MySQL,心想这样应该能省点事。
3.1 初始化实例时开启兼容模式
达梦的初始化工具叫dminit,在安装目录的bin下面。我们可以用下面这个命令初始化一个数据目录:
# 技术栈:Shell / 达梦数据库
# 初始化DM实例,指定路径、页大小、字符集、兼容模式
/home/dmdbms/bin/dminit PATH=/dm/data PAGE_SIZE=32 CHAR_CODE=PG_UTF8 COMPATIBLE_MODE=3
这里COMPATIBLE_MODE=3就是兼容MySQL。如果设为1,则兼容Oracle。如果是0,那就是原生模式。设置成3以后,很多MySQL的语法可以直接跑,但并不是所有语法都能完全兼容。
3.2 建库建用户
初始化完实例,启动数据库服务。然后我们用disql连接,创建一个给禅道用的用户和表空间:
-- 技术栈:SQL / 达梦数据库
-- 创建表空间,相当于MySQL的database
CREATE TABLESPACE ZENTAO DATAFILE '/dm/data/zentao.dbf' SIZE 1G AUTOEXTEND ON NEXT 100M;
-- 创建用户,密码要符合达梦的复杂度要求
CREATE USER "ZENTAO" IDENTIFIED BY "Zentao@123" DEFAULT TABLESPACE "ZENTAO";
-- 给用户DBA权限,省得后续一堆权限问题
GRANT DBA TO "ZENTAO";
注意,达梦双引号里的大小写敏感。我们用大写,后面禅道配置里用户名也要大写,不然会提示用户不存在。
四、禅道连接达梦:先过驱动这一关
禅道默认的数据库配置文件在config/my.php。它原本是长这样的:
<?php
// config/my.php 技术栈:PHP + 禅道
// 这是默认的MySQL连接配置
return array(
'default' => array(
'driver' => 'mysql',
'host' => '127.0.0.1',
'port' => '3306',
'user' => 'zentao',
'password' => 'Zentao@123',
'dbname' => 'zentao',
'charset' => 'utf8',
),
);
我们要把它改成达梦的连接方式。但禅道内核用的是PDO抽象层,它不认识dm这个驱动名。我们需要先让PHP环境里多出pdo_dm扩展。
4.1 编译安装PDO_DM扩展
达梦官方提供了PHP扩展源码,一般放在安装目录下的samples/php/pdo_dm。进入这个目录编译:
# 技术栈:Shell / PHP扩展编译
# 设置PHP的相关工具路径
export PHP_PREFIX=/usr/local/php
cd /home/dmdbms/samples/php/pdo_dm
$PHP_PREFIX/bin/phpize
./configure --with-php-config=$PHP_PREFIX/bin/php-config --with-dm=/home/dmdbms
make && make install
# 在php.ini末尾加上扩展配置
echo "extension=pdo_dm.so" >> $PHP_PREFIX/etc/php.ini
编译完成后,重启PHP-FPM,然后检查扩展是否加载:
# 技术栈:Shell / PHP
# 确认pdo_dm已加载
php -m | grep pdo_dm
如果看到pdo_dm,说明驱动已经就位。否则,要检查开发包是否完整,比如php-dev,还有达梦动态库路径。
4.2 修改禅道配置文件
现在,我们把my.php里的driver改成dm,端口改成5236:
<?php
// config/my.php 技术栈:PHP + 禅道
// 修改后的达梦连接配置
return array(
'default' => array(
'driver' => 'dm', // 驱动类型换成 dm
'host' => '192.168.1.10', // 达梦数据库服务器地址
'port' => '5236', // 达梦默认端口
'user' => 'ZENTAO', // 注意大小写,要与建库时一致
'password' => 'Zentao@123',
'dbname' => 'ZENTAO', // 达梦的“库”其实是模式
'charset' => 'utf8',
),
);
改完后,我们启动禅道install页面,发现页面能正常显示,但点击安装后,一直报错,提示某个表不存在。我们又看了一眼错误日志,发现原来禅道用的还是MySQL语法去建表,比如AUTO_INCREMENT、反引号,还有ENGINE=InnoDB DEFAULT CHARSET=utf8这些尾巴。达梦根本不认。
所以,光改连接配置还不够,后面才是真正的硬仗。
五、真正的坑点来了:SQL语句一个接一个报错
之前我们以为开启兼容模式就万事大吉,结果现实给了我们一巴掌。下面我把我们踩到的主要坑列出来,并给出解决办法。
5.1 反引号与大小写问题
MySQL里习惯用反引号包裹字段名,比如:
-- 技术栈:SQL / MySQL风格
SELECT `id`, `name` FROM `zt_user`;
达梦默认不认反引号,会报语法错误。虽然兼容模式能处理一些,但我们遇到很多场景仍然会出错。
最简单的办法是,把所有反引号去掉。因为禅道没有用保留字做字段名,去掉反引号不影响语义。我们写了一个PHP脚本,在禅道启动时自动把SQL里的反引号替换成双引号。其实双引号在达梦里也代表标识符,同样能解决大小写问题。但这时候字段大小写必须和数据库定义完全一致。
我们可以用一个简单的自动修正函数:
<?php
// 技术栈:PHP
// 自动修正禅道生成的SQL,让达梦能接受
function fixSQLForDM($sql) {
// 1. 把反引号替换成双引号(达梦标识符专用)
$sql = str_replace('`', '"', $sql);
// 2. 把MySQL的NOW()替换成达梦的CURRENT_TIMESTAMP
$sql = str_replace('NOW()', 'CURRENT_TIMESTAMP', $sql);
// 3. 把MySQL的IFNULL替换成达梦的NVL
$sql = str_replace('IFNULL', 'NVL', $sql);
// 4. 删除表结尾的 ENGINE=... 和 CHARSET=... 尾巴
$sql = preg_replace('/ENGINE\s*=\s*[^\s;]+/i', '', $sql);
$sql = preg_replace('/DEFAULT\s+CHARSET\s*=\s*[^\s;]+/i', '', $sql);
return $sql;
}
至于这个函数怎么集成到禅道里,我们需要修改禅道的数据库类。具体位置是lib/base/dao/dao.class.php,在query方法里调用一下fixSQLForDM。当然,这是暴力方案,但能解决大部分问题。
5.2 自增主键怎么建?
禅道在安装时,需要创建几十张表。MySQL风格的自增主键写法是:
-- 技术栈:SQL / MySQL风格
CREATE TABLE zt_user (
id int(11) NOT NULL AUTO_INCREMENT,
account varchar(30) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
这段SQL在达梦里根本跑不过,因为达梦不支持AUTO_INCREMENT,也不支持ENGINE。我们需要改成这样的达梦版本:
-- 技术栈:SQL / 达梦风格
CREATE TABLE zt_user (
id INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
account VARCHAR(30) NOT NULL
);
这里IDENTITY(1,1)就相当于MySQL的自增,从1开始,每次加1。但问题是,禅道安装时不会用我们改后的SQL。所以我们只能提前在达梦里把所有表建好?那太费事。
更好的方案是,在禅道的建表类里,把创建表语句的转换逻辑加进去。比如在lib/base/install/install.class.php里找到执行建表的函数,在生成SQL后调用我们的fixCreateTableSQL函数:
<?php
// 技术栈:PHP
// 把MySQL建表语句转换成达梦风格
function fixCreateTableSQL($sql) {
// 去掉末尾的 ENGINE=... DEFAULT CHARSET=... 等
$sql = preg_replace('/ENGINE\s*=\s*[^\s;]+/i', '', $sql);
$sql = preg_replace('/DEFAULT\s+CHARSET\s*=\s*[^\s;]+/i', '', $sql);
// 将 int(11) 转成 int,因为达梦不支持(11)
$sql = preg_replace('/int\s*\(\d+\)/i', 'INT', $sql);
// 将 AUTO_INCREMENT 替换为 IDENTITY(1,1)
$sql = preg_replace('/AUTO_INCREMENT/i', 'IDENTITY(1,1)', $sql);
// 返回转换后的结果
return $sql;
}
这样至少能让装环境成功。
5.3 函数不兼容怎么办?
禅道代码中用了很多MySQL特有函数。比如:
NOW()->CURRENT_TIMESTAMPIFNULL()->NVL()或COALESCE()GROUP_CONCAT()->LISTAGG()LIMIT offset, count-> 达梦兼容模式支持LIMIT,但有时要写成OFFSET格式。
比如禅道里获取用户列表的分页语句是:
-- 技术栈:SQL / MySQL风格
SELECT * FROM zt_user ORDER BY id DESC LIMIT 10, 20;
在达梦兼容模式里,这样的语法可能会报错。我们需要改成:
-- 技术栈:SQL / 达梦风格
SELECT * FROM zt_user ORDER BY id DESC LIMIT 20 OFFSET 10;
或者使用达梦自己的分页写法ROWNUM。为了少改代码,我们建议在SQL翻译层把LIMIT offset, count改写成LIMIT count OFFSET offset。同样,写个正则:
<?php
// 技术栈:PHP
// 将 LIMIT offset, count 转换成 DM 支持的 LIMIT count OFFSET offset
function fixLimitClause($sql) {
$pattern = '/LIMIT\s+(\d+)\s*,\s*(\d+)/i';
$sql = preg_replace_callback($pattern, function($matches) {
$offset = $matches[1];
$count = $matches[2];
return "LIMIT $count OFFSET $offset";
}, $sql);
return $sql;
}
这些函数都挂到之前的fixSQLForDM里就行。
六、数据迁移:把老数据从MySQL搬到达梦
把代码适配好后,还要把原来的数据迁过去。这里我们用了达梦自带的DTS迁移工具,原理就是连接MySQL,然后同步到DM。
6.1 使用DTS迁移工具
DTS是达梦提供的一个图形化工具,操作路径大概是:打开DTS,新建迁移任务,源数据库选择MySQL,输入老数据库的地址、账号、密码,目标库选择达梦,然后选择需要迁移的表。因为代码层面已经做了转换,表结构在迁移时最好也使用转换后的建表语句。我们可以先只在DTS里迁移数据,表结构用修改后的SQL建。
一个非常关键的点是:DTS迁移时,对于字段类型可能会擅自转换,比如MySQL的tinyint会转成smallint,导致后续存储数据时出现溢出。所以迁移完,必须做一次数据校验。
6.2 迁移后的校验
写一个简单的SQL脚本来核对几张大表的数据量:
-- 技术栈:SQL / 达梦数据库
-- 在MySQL里执行,得到源数据的行数
SELECT COUNT(*) AS user_count FROM zt_user;
-- 在达梦里执行,得到目标数据行数
SELECT COUNT(*) AS user_count FROM "ZENTAO"."zt_user";
如果两者不一致,多半是迁移时主键冲突或字段截断,需要重新迁移。
七、信创操作系统的那些“小惊喜”
数据库搞通了,代码也能跑了,但整个过程还伴随着系统层面的坑。这里挑两个最常见的。
7.1 SELinux和防火墙
银河麒麟V10默认开了SELinux,PHP连接达梦的请求会被拦截,表现为PHP页面一直卡死或连接超时。我们一开始排查了很久,后来用setenforce 0关掉才正常。
# 技术栈:Shell / 银河麒麟V10
# 临时关闭SELinux
sudo setenforce 0
# 检查状态
getenforce
同时,内网机器的防火墙也要放行达梦端口和PHP端口:
# 技术栈:Shell / 防火墙
# 放行达梦端口
sudo firewall-cmd --permanent --add-port=5236/tcp
# 放行PHP-FPM端口(如9000)
sudo firewall-cmd --permanent --add-port=9000/tcp
sudo firewall-cmd --reload
7.2 PHP扩展缺失
在信创系统上,达梦的PHP扩展需要重新编译。我们用的是ARM版银河麒麟,编译前需要确保系统装好gcc、make和PHP开发包:
# 技术栈:Shell / ARM环境
# 安装编译工具
sudo yum install -y gcc make php-devel
这里要注意,统信UOS和银河麒麟的包管理器可能不同,但基本命令类似。如果出现编译错误,先检查PHP版本是否一致。我们一开始在统信上用PHP 7.3编译的扩展,拿到麒麟上装的是7.4,导致加载不了。所以一定得在目标机器上编译,或者直接用相同版本预编译。
八、应用场景总结:什么情况适合这么干?
这套方案适合那些必须在信创环境下运行、但对实时性要求不高的中小型项目管理工具。比如内部使用的禅道、Bugzilla这类。因为改动量大,不建议在高并发、核心交易链路上使用。
对于禅道本身,如果你已经有成熟的MySQL环境,而且公司没有硬性规定,别折腾达梦。但如果有硬性要求,我们这套“连接达梦+SQL翻译+自定义建表”的方案,至少能让它跑起来。适合团队人数不超过几十人、数据量几GB以内的场景。
九、禅道+达梦的优缺点分析
优点:
- 满足了信创合规要求,避免了政策风险。
- 达梦在数据安全防护方面还是有可取之处的,支持透明加密、审计等功能。
- 性能在低并发场景下和MySQL差不多,甚至某些复杂查询在达梦的优化器下还有提升。
缺点:
- 适配成本高。我们用了两个多星期才把坑填完,后期维护也需要懂达梦的人。
- 生态不成熟。很多现成的PHP类库都优先兼容MySQL,遇到问题只能自己改。
- SQL方言差异大,即使开启了兼容模式,仍然需要大量代码修改。
- 社区资源少。网上关于“禅道+达梦”的案例几乎没有,你只能自己去趟坑。
十、注意事项:给后来者提个醒
- 先确认版本兼容。达梦DM8不同的版本,对PHP扩展的要求不一样,下载源码时一定要找对版本。
- 不要完全依赖COMPATIBLE_MODE。它只能解决一部分常见SQL语法,像存储过程、触发器这些高级特性,兼容得并不好。
- 注意大小写和双引号。达梦在默认情况下,不带引号的标识符会转成大写。所以禅道代码里的小写表名,尽量通过配置或预处理统一。
- 备份你的修改。我们会把禅道框架里所有改过的文件单独备份,因为升级时这些修改会被覆盖,导致系统崩溃。
- 先做小批量数据迁移测试,再上全量。直接拿生产数据迁移,失败了很难回滚。
- 性能调优时,重点看索引和SQL执行计划。达梦的索引和MySQL的InnoDB有差异,原来好用的索引可能需要重建。
- 如果公司条件允许,可以考虑直接用禅道官方的企业版,他们好像有适配信创的版本,但价格不便宜。
十一、总结
这次国产化之路,不能说毫无收获,至少我们对达梦数据库有了深入的了解。但从实际效果来看,把一个原生MySQL应用强行往达梦上搬,付出的代价远比预想大。希望这篇文章能帮到有同样遭遇的人。也许未来禅道会有官方达梦支持,但在那之前,我们只能靠这些“小补丁”撑着。
评论
围绕“公司强制要求国产化替代?禅道在信创环境麒麟统信加达梦数据库的适配与坑点”参与讨论