最近公司响应信创号召,要求所有内部系统换成国产软硬件环境。我们团队负责的禅道项目管理系统,就这么被推到了风口浪尖上。虽然表面上只是把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_TIMESTAMP
  • IFNULL() -> 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方言差异大,即使开启了兼容模式,仍然需要大量代码修改。
  • 社区资源少。网上关于“禅道+达梦”的案例几乎没有,你只能自己去趟坑。

十、注意事项:给后来者提个醒

  1. 先确认版本兼容。达梦DM8不同的版本,对PHP扩展的要求不一样,下载源码时一定要找对版本。
  2. 不要完全依赖COMPATIBLE_MODE。它只能解决一部分常见SQL语法,像存储过程、触发器这些高级特性,兼容得并不好。
  3. 注意大小写和双引号。达梦在默认情况下,不带引号的标识符会转成大写。所以禅道代码里的小写表名,尽量通过配置或预处理统一。
  4. 备份你的修改。我们会把禅道框架里所有改过的文件单独备份,因为升级时这些修改会被覆盖,导致系统崩溃。
  5. 先做小批量数据迁移测试,再上全量。直接拿生产数据迁移,失败了很难回滚。
  6. 性能调优时,重点看索引和SQL执行计划。达梦的索引和MySQL的InnoDB有差异,原来好用的索引可能需要重建。
  7. 如果公司条件允许,可以考虑直接用禅道官方的企业版,他们好像有适配信创的版本,但价格不便宜。

十一、总结

这次国产化之路,不能说毫无收获,至少我们对达梦数据库有了深入的了解。但从实际效果来看,把一个原生MySQL应用强行往达梦上搬,付出的代价远比预想大。希望这篇文章能帮到有同样遭遇的人。也许未来禅道会有官方达梦支持,但在那之前,我们只能靠这些“小补丁”撑着。