aiccs-api/check/跨数据库SQL兼容性检测方案.md

24 KiB
Raw Blame History

跨数据库SQL兼容性检测方案

一、检测目标

检测系统中所有SQL语句在目标数据库达梦/MySQL/Oracle/DB2上的兼容性识别并修复不兼容的SQL语法确保系统平滑迁移到新数据库环境。


二、检测范围

检测对象

  1. Mapper XML文件所有MyBatis Mapper XML中的SQL语句
  2. Java代码:使用注解(@Select/@Insert/@Update/@Delete的SQL
  3. 动态SQLJava代码中拼接的SQL字符串
  4. 数据库脚本DDL/DML脚本文件

检测项

  1. SQL语法兼容性

    • MySQL特有语法 → 达梦/Oracle语法
    • 分页、函数、操作符等
  2. 数据类型兼容性

    • MySQL特有类型 → 标准SQL类型
    • 类型映射关系
  3. 字符串和日期处理

    • 字符串拼接、日期函数等

三、MySQL到达梦兼容性对照表

SQL类型 MySQL语法 达梦语法 兼容性说明 检测方法
分页 LIMIT 10, 20 LIMIT 20 OFFSET 10ROWNUM 达梦8+支持LIMIT Grep搜索LIMIT
自增主键 AUTO_INCREMENT IDENTITYSEQUENCE 需修改为IDENTITY Grep搜索AUTO_INCREMENT
日期函数 NOW() SYSDATE 需替换为SYSDATE Grep搜索NOW()
字符串拼接 CONCAT(a,b)CONCAT_WS() a || bCONCAT() 达梦支持两种方式 Grep搜索CONCAT
反引号 `table` "table" 需替换为双引号 Grep搜索反引号
布尔值 TRUE/FALSE 1/0 需替换为1/0 Grep搜索TRUE|FALSE
注释符号 #注释 -- 注释 #不支持,需改为-- Grep搜索^[[:space:]]*#
当前时间 CURRENT_TIMESTAMP SYSDATE 建议统一用SYSDATE Grep搜索CURRENT_TIMESTAMP
空字符串 '' ''NULL 大部分兼容 人工检查
数据类型 TINYINT/MEDIUMINT TINYINT/INT TINYINT需调整为INT Grep搜索TINYINT|MEDIUMINT
索引提示 USE INDEX 不支持 需删除索引提示 Grep搜索USE INDEX
GROUP BY 允许SELECT非聚合字段 要求SELECT字段都在GROUP BY中 需修改SQL 人工检查
IF函数 IF(condition, a, b) CASE WHEN 需改写为CASE WHEN Grep搜索\bIF\(
日期格式化 DATE_FORMAT() TO_CHAR() 需改写为TO_CHAR Grep搜索DATE_FORMAT
字符串转数字 CAST('123' AS SIGNED) CAST('123' AS INT) SIGNED改为INT Grep搜索AS SIGNED

四、检测方法

方案A命令行快速扫描

#!/bin/bash
# MySQL到达梦SQL兼容性扫描脚本

echo "开始扫描MySQL特有SQL语法..."
echo ""

# 1. 搜索分页语句LIMIT
echo "=== 1. 分页语句 (LIMIT) ==="
grep -rn "LIMIT\s" src/main/resources/mybatis/
echo ""

# 2. 搜索自增主键AUTO_INCREMENT
echo "=== 2. 自增主键 (AUTO_INCREMENT) ==="
grep -rn "AUTO_INCREMENT" src/
echo ""

# 3. 搜索日期函数NOW
echo "=== 3. 日期函数 (NOW) ==="
grep -rn "NOW()" src/
echo ""

# 4. 搜索反引号MySQL特有
echo "=== 4. 反引号 (MySQL特有) ==="
grep -rn '`' src/main/resources/mybatis/
echo ""

# 5. 搜索布尔值TRUE/FALSE
echo "=== 5. 布尔值 (TRUE/FALSE) ==="
grep -rn "\bTRUE\b\|\bFALSE\b" src/
echo ""

# 6. 搜索注释符号(#
echo "=== 6. 注释符号 (#) ==="
grep -rn "^[[:space:]]*#" src/main/resources/mybatis/
echo ""

# 7. 搜索特定数据类型
echo "=== 7. MySQL特有数据类型 (TINYINT/MEDIUMINT) ==="
grep -rn "TINYINT\|MEDIUMINT" src/
echo ""

# 8. 搜索IF函数
echo "=== 8. IF函数 (需改写为CASE WHEN) ==="
grep -rn "\bIF(" src/
echo ""

# 9. 搜索DATE_FORMAT
echo "=== 9. DATE_FORMAT (需改写为TO_CHAR) ==="
grep -rn "DATE_FORMAT" src/
echo ""

# 10. 搜索CAST AS SIGNED
echo "=== 10. CAST AS SIGNED (需改为AS INT) ==="
grep -rn "AS SIGNED" src/
echo ""

# 11. 搜索USE INDEX
echo "=== 11. USE INDEX (达梦不支持) ==="
grep -rn "USE INDEX\|FORCE INDEX\|IGNORE INDEX" src/
echo ""

# 12. 搜索GROUP_CONCAT
echo "=== 12. GROUP_CONCAT (需改写为LISTAGG) ==="
grep -rn "GROUP_CONCAT" src/
echo ""

echo "扫描完成!"

Windows版本PowerShell

# MySQL到达梦SQL兼容性扫描脚本

Write-Host "开始扫描MySQL特有SQL语法..." -ForegroundColor Green
Write-Host ""

# 定义要扫描的模式
$patterns = @{
    "分页语句 (LIMIT)" = "LIMIT\s"
    "自增主键 (AUTO_INCREMENT)" = "AUTO_INCREMENT"
    "日期函数 (NOW)" = "NOW\(\)"
    "反引号" = "`""
    "布尔值 (TRUE/FALSE)" = "\bTRUE\b|\bFALSE\b"
    "注释符号 (#)" = "^\s*#"
    "TINYINT/MEDIUMINT" = "TINYINT|MEDIUMINT"
    "IF函数" = "\bIF\("
    "DATE_FORMAT" = "DATE_FORMAT"
    "CAST AS SIGNED" = "AS SIGNED"
    "USE INDEX" = "USE INDEX|FORCE INDEX|IGNORE INDEX"
    "GROUP_CONCAT" = "GROUP_CONCAT"
}

# 执行扫描
foreach ($item in $patterns.GetEnumerator()) {
    Write-Host "=== $($item.Key) ===" -ForegroundColor Yellow
    Select-String -Path "src\**\*.xml", "src\**\*.java" -Pattern $item.Value -Recurse
    Write-Host ""
}

Write-Host "扫描完成!" -ForegroundColor Green

方案BPython自动化检测工具

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MySQL到达梦SQL兼容性检测工具
"""

import re
import json
from pathlib import Path
from typing import Dict, List, Tuple
from dataclasses import dataclass, asdict
from datetime import datetime


@dataclass
class CompatibilityIssue:
    """兼容性问题"""
    issue_type: str
    severity: str  # HIGH, MEDIUM, LOW
    file_path: str
    line_number: int
    mysql_pattern: str
    dm_pattern: str
    description: str
    suggestion: str


class MySQLToDMCompatibilityChecker:
    """MySQL到达梦兼容性检测器"""

    # 检测规则配置
    RULES = {
        'LIMIT': {
            'pattern': r'\bLIMIT\s+(\d+)\s*,\s*(\d+)',
            'severity': 'HIGH',
            'mysql_pattern': 'LIMIT offset, count',
            'dm_pattern': 'LIMIT count OFFSET offset',
            'description': 'MySQL分页语法不兼容达梦',
            'suggestion': '将 LIMIT offset, count 改为 LIMIT count OFFSET offset'
        },
        'AUTO_INCREMENT': {
            'pattern': r'\bAUTO_INCREMENT\b',
            'severity': 'HIGH',
            'mysql_pattern': 'AUTO_INCREMENT',
            'dm_pattern': 'IDENTITY',
            'description': '达梦不支持AUTO_INCREMENT',
            'suggestion': '将 AUTO_INCREMENT 改为 IDENTITY 或使用序列SEQUENCE'
        },
        'NOW': {
            'pattern': r'\bNOW\(\)',
            'severity': 'HIGH',
            'mysql_pattern': 'NOW()',
            'dm_pattern': 'SYSDATE',
            'description': '达梦不支持NOW()函数',
            'suggestion': '将 NOW() 改为 SYSDATE'
        },
        'BACKTICK': {
            'pattern': r'`',
            'severity': 'HIGH',
            'mysql_pattern': '`table_name`',
            'dm_pattern': '"table_name"',
            'description': 'MySQL反引号语法',
            'suggestion': '将反引号改为双引号 "table_name"'
        },
        'BOOLEAN': {
            'pattern': r'\b(TRUE|FALSE)\b',
            'severity': 'MEDIUM',
            'mysql_pattern': 'TRUE/FALSE',
            'dm_pattern': '1/0',
            'description': '达梦不支持布尔值关键字',
            'suggestion': '将 TRUE 改为 1FALSE 改为 0'
        },
        'COMMENT_HASH': {
            'pattern': r'^\s*#',
            'severity': 'LOW',
            'mysql_pattern': '# 注释',
            'dm_pattern': '-- 注释',
            'description': '达梦不支持#注释',
            'suggestion': '将 # 注释 改为 -- 注释'
        },
        'TINYINT': {
            'pattern': r'\bTINYINT\b',
            'severity': 'MEDIUM',
            'mysql_pattern': 'TINYINT',
            'dm_pattern': 'INT或SMALLINT',
            'description': '达梦不完全支持TINYINT',
            'suggestion': '将 TINYINT 改为 SMALLINT 或 INT'
        },
        'IF_FUNCTION': {
            'pattern': r'\bIF\s*\(',
            'severity': 'HIGH',
            'mysql_pattern': 'IF(condition, a, b)',
            'dm_pattern': 'CASE WHEN condition THEN a ELSE b END',
            'description': '达梦不支持IF()函数',
            'suggestion': '将 IF(condition, a, b) 改为 CASE WHEN condition THEN a ELSE b END'
        },
        'DATE_FORMAT': {
            'pattern': r'\bDATE_FORMAT\s*\(',
            'severity': 'HIGH',
            'mysql_pattern': 'DATE_FORMAT(date, format)',
            'dm_pattern': 'TO_CHAR(date, format)',
            'description': '达梦不支持DATE_FORMAT函数',
            'suggestion': '将 DATE_FORMAT(date, format) 改为 TO_CHAR(date, format)'
        },
        'CAST_SIGNED': {
            'pattern': r'\bCAST\s*\([^)]+\s+AS\s+SIGNED\b',
            'severity': 'MEDIUM',
            'mysql_pattern': 'CAST(expr AS SIGNED)',
            'dm_pattern': 'CAST(expr AS INT)',
            'description': '达梦不支持SIGNED类型',
            'suggestion': '将 CAST(expr AS SIGNED) 改为 CAST(expr AS INT)'
        },
        'USE_INDEX': {
            'pattern': r'\b(USE|FORCE|IGNORE)\s+INDEX\b',
            'severity': 'MEDIUM',
            'mysql_pattern': 'USE INDEX (index_name)',
            'dm_pattern': '(删除索引提示)',
            'description': '达梦不支持索引提示',
            'suggestion': '删除 USE INDEX/FORCE INDEX/IGNORE INDEX'
        },
        'GROUP_CONCAT': {
            'pattern': r'\bGROUP_CONCAT\s*\(',
            'severity': 'HIGH',
            'mysql_pattern': 'GROUP_CONCAT(expr)',
            'dm_pattern': 'LISTAGG(expr, delimiter)',
            'description': '达梦不支持GROUP_CONCAT函数',
            'suggestion': '将 GROUP_CONCAT(expr) 改为 LISTAGG(expr, ",")'
        }
    }

    def __init__(self, source_dir: str):
        """
        初始化检测器

        :param source_dir: 源代码目录
        """
        self.source_dir = Path(source_dir)
        self.issues: List[CompatibilityIssue] = []

    def check_file(self, file_path: Path) -> List[CompatibilityIssue]:
        """检测单个文件"""
        issues = []
        content = file_path.read_text(encoding='utf-8')
        lines = content.split('\n')

        for line_no, line in enumerate(lines, 1):
            for rule_name, rule in self.RULES.items():
                matches = re.finditer(rule['pattern'], line, re.IGNORECASE)
                for match in matches:
                    issues.append(CompatibilityIssue(
                        issue_type=rule_name,
                        severity=rule['severity'],
                        file_path=str(file_path.relative_to(self.source_dir)),
                        line_number=line_no,
                        mysql_pattern=rule['mysql_pattern'],
                        dm_pattern=rule['dm_pattern'],
                        description=rule['description'],
                        suggestion=rule['suggestion']
                    ))

        return issues

    def check_all(self) -> List[CompatibilityIssue]:
        """检测所有文件"""
        # 检测XML文件
        xml_files = list(self.source_dir.rglob('**/*.xml'))
        # 检测Java文件
        java_files = list(self.source_dir.rglob('**/*.java'))

        all_files = xml_files + java_files

        print(f'开始检测 {len(all_files)} 个文件...\n')

        for idx, file_path in enumerate(all_files, 1):
            print(f'[{idx}/{len(all_files)}] 检测: {file_path.name}')
            issues = self.check_file(file_path)
            self.issues.extend(issues)

        return self.issues

    def generate_report(self, output_file: str = 'compatibility_report.json'):
        """生成检测报告"""
        report = {
            'scan_time': datetime.now().isoformat(),
            'summary': {
                'total_issues': len(self.issues),
                'by_severity': self._count_by_severity(),
                'by_type': self._count_by_type(),
                'by_file': self._count_by_file()
            },
            'issues': [asdict(issue) for issue in self.issues]
        }

        # 保存JSON报告
        with open(output_file, 'w', encoding='utf-8') as f:
            json.dump(report, f, ensure_ascii=False, indent=2)

        # 生成Markdown报告
        md_file = output_file.replace('.json', '.md')
        self._generate_markdown_report(report, md_file)

        print(f'\n检测完成!')
        print(f'JSON报告: {output_file}')
        print(f'Markdown报告: {md_file}')
        print(f'\n总计发现 {report["summary"]["total_issues"]} 个兼容性问题:')
        for severity, count in report['summary']['by_severity'].items():
            print(f'  - {severity}: {count}')

    def _count_by_severity(self) -> Dict[str, int]:
        """按严重程度统计"""
        count = {'HIGH': 0, 'MEDIUM': 0, 'LOW': 0}
        for issue in self.issues:
            count[issue.severity] += 1
        return count

    def _count_by_type(self) -> Dict[str, int]:
        """按问题类型统计"""
        count = {}
        for issue in self.issues:
            if issue.issue_type not in count:
                count[issue.issue_type] = 0
            count[issue.issue_type] += 1
        return dict(sorted(count.items(), key=lambda x: x[1], reverse=True))

    def _count_by_file(self) -> Dict[str, int]:
        """按文件统计"""
        count = {}
        for issue in self.issues:
            if issue.file_path not in count:
                count[issue.file_path] = 0
            count[issue.file_path] += 1
        return dict(sorted(count.items(), key=lambda x: x[1], reverse=True)[:10])

    def _generate_markdown_report(self, report: dict, output_file: str):
        """生成Markdown格式报告"""
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write('# MySQL到达梦SQL兼容性检测报告\n\n')
            f.write(f'**扫描时间**: {report["scan_time"]}\n\n')

            # 摘要
            f.write('## 检测摘要\n\n')
            f.write(f'- **总问题数**: {report["summary"]["total_issues"]}\n')
            f.write('\n### 按严重程度\n\n')
            f.write('| 严重程度 | 数量 |\n')
            f.write('|---------|------|\n')
            for severity, count in report['summary']['by_severity'].items():
                f.write(f'| {severity} | {count} |\n')

            f.write('\n### 按问题类型\n\n')
            f.write('| 问题类型 | 数量 |\n')
            f.write('|---------|------|\n')
            for issue_type, count in list(report['summary']['by_type'].items())[:10]:
                f.write(f'| {issue_type} | {count} |\n')

            f.write('\n### 问题最多的文件 (Top 10)\n\n')
            f.write('| 文件 | 问题数 |\n')
            f.write('|------|--------|\n')
            for file_path, count in report['summary']['by_file'].items():
                f.write(f'| {file_path} | {count} |\n')

            # 详细问题列表
            f.write('\n## 详细问题清单\n\n')
            for issue in report['issues']:
                f.write(f'### {issue["issue_type"]} - {issue["severity"]}\n\n')
                f.write(f'**文件**: `{issue["file_path"]}:{issue["line_number"]}`\n\n')
                f.write(f'**问题描述**: {issue["description"]}\n\n')
                f.write(f'- **MySQL语法**: `{issue["mysql_pattern"]}`\n')
                f.write(f'- **达梦语法**: `{issue["dm_pattern"]}`\n')
                f.write(f'- **修复建议**: {issue["suggestion"]}\n\n')
                f.write('---\n\n')

    def generate_fix_script(self, output_file: str = 'fix_compatibility.sh'):
        """生成自动修复脚本(仅适用于简单替换)"""
        with open(output_file, 'w', encoding='utf-8') as f:
            f.write('#!/bin/bash\n')
            f.write('# MySQL到达梦SQL兼容性自动修复脚本\n')
            f.write('# 注意:此脚本仅处理简单替换,复杂情况需人工确认\n\n')

            # 可自动修复的规则
            fixable_rules = {
                'NOW': ('NOW()', 'SYSDATE'),
                'AUTO_INCREMENT': ('AUTO_INCREMENT', 'IDENTITY'),
                'BOOLEAN_TRUE': ('\\bTRUE\\b', '1'),
                'BOOLEAN_FALSE': ('\\bFALSE\\b', '0'),
            }

            for rule_name, (old, new) in fixable_rules.items():
                f.write(f'# 修复 {rule_name}\n')
                f.write(f'find src -type f -name "*.xml" -o -name "*.java" | \\\n')
                f.write(f'  xargs sed -i "s/{old}/{new}/g"\n\n')

            f.write('echo "自动修复完成!请仔细检查修改内容。"\n')

        print(f'自动修复脚本已生成: {output_file}')
        print('⚠️  注意:使用前请先提交代码,修复脚本可能产生副作用')


def main():
    """主函数"""
    import sys

    # 配置
    SOURCE_DIR = 'src'
    JSON_REPORT = 'check/reports/compatibility_report.json'
    FIX_SCRIPT = 'check/scripts/fix_compatibility.sh'

    # 创建检测器
    checker = MySQLToDMCompatibilityChecker(SOURCE_DIR)

    # 执行检测
    checker.check_all()

    # 生成报告
    checker.generate_report(JSON_REPORT)

    # 生成修复脚本
    checker.generate_fix_script(FIX_SCRIPT)


if __name__ == '__main__':
    main()

五、常见SQL兼容性问题及修复示例

1. 分页查询

MySQL写法

<select id="selectPage" resultType="...">
    SELECT * FROM abnormal_list
    LIMIT #{offset}, #{pageSize}
</select>

达梦写法

<select id="selectPage" resultType="...">
    SELECT * FROM abnormal_list
    LIMIT #{pageSize} OFFSET #{offset}
</select>

或者使用ROWNUM达梦7及以下

<select id="selectPage" resultType="...">
    SELECT * FROM (
        SELECT t.*, ROWNUM AS rn FROM abnormal_list t WHERE ROWNUM <= #{offset} + #{pageSize}
    ) WHERE rn > #{offset}
</select>

2. 日期函数

MySQL写法

<select id="selectByCreateTime" resultType="...">
    SELECT * FROM abnormal_list
    WHERE create_time &lt;= NOW()
</select>

达梦写法

<select id="selectByCreateTime" resultType="...">
    SELECT * FROM abnormal_list
    WHERE create_time &lt;= SYSDATE
</select>

3. IF函数改为CASE WHEN

MySQL写法

<select id="selectWithStatus" resultType="...">
    SELECT
        entname,
        IF(abnormal_date IS NULL, '正常', '异常') AS status
    FROM abnormal_list
</select>

达梦写法

<select id="selectWithStatus" resultType="...">
    SELECT
        entname,
        CASE WHEN abnormal_date IS NULL THEN '正常' ELSE '异常' END AS status
    FROM abnormal_list
</select>

4. GROUP_CONCAT改为LISTAGG

MySQL写法

<select id="selectGroupConcat" resultType="...">
    SELECT
        dept_id,
        GROUP_CONCAT(entname SEPARATOR ',') AS ent_names
    FROM abnormal_list
    GROUP BY dept_id
</select>

达梦写法

<select id="selectGroupConcat" resultType="...">
    SELECT
        dept_id,
        LISTAGG(entname, ',') WITHIN GROUP (ORDER BY entname) AS ent_names
    FROM abnormal_list
    GROUP BY dept_id
</select>

5. 字符串拼接

MySQL写法

<select id="selectFullName" resultType="...">
    SELECT
        CONCAT(last_name, first_name) AS full_name
    FROM users
</select>

达梦写法(两种都支持):

<select id="selectFullName" resultType="...">
    SELECT
        last_name || first_name AS full_name
    FROM users
</select>

<select id="selectFullName" resultType="...">
    SELECT
        CONCAT(last_name, first_name) AS full_name
    FROM users
</select>

6. CAST类型转换

MySQL写法

<select id="selectCast" resultType="...">
    SELECT CAST(amount AS SIGNED) AS int_amount
    FROM payments
</select>

达梦写法

<select id="selectCast" resultType="...">
    SELECT CAST(amount AS INT) AS int_amount
    FROM payments
</select>

7. DATE_FORMAT改为TO_CHAR

MySQL写法

<select id="selectFormattedDate" resultType="...">
    SELECT
        DATE_FORMAT(create_time, '%Y-%m-%d') AS formatted_date
    FROM abnormal_list
</select>

达梦写法

<select id="selectFormattedDate" resultType="...">
    SELECT
        TO_CHAR(create_time, 'YYYY-MM-DD') AS formatted_date
    FROM abnormal_list
</select>

六、检测步骤

1. 准备阶段

# 创建报告目录
mkdir -p check/reports
mkdir -p check/scripts

# 统计需要检测的文件数量
find src -name "*.xml" | wc -l
find src -name "*.java" | wc -l

2. 执行检测

方式1使用命令行快速扫描

# Linux/Mac
bash check/scripts/scan_mysql_syntax.sh > scan_results.txt

# Windows PowerShell
powershell -ExecutionPolicy Bypass -File check/scripts/scan_mysql_syntax.ps1 > scan_results.txt

方式2使用Python自动化检测

# 运行检测脚本
python check/scripts/mysql_to_dm_checker.py

# 查看报告
cat check/reports/compatibility_report.json
cat check/reports/compatibility_report.md

3. 分析报告

根据报告中的问题优先级处理:

  1. HIGH高危

    • 影响SQL执行的关键语法
    • 必须修复才能正常运行
  2. MEDIUM中危

    • 可能有兼容性问题
    • 建议修复
  3. LOW低危

    • 次要兼容性问题
    • 可选修复

4. 修复问题

自动修复(谨慎使用):

# 先提交代码
git add .
git commit -m "备份:修复前的代码"

# 执行自动修复脚本
bash check/scripts/fix_compatibility.sh

# 检查修复结果
git diff

手动修复 根据报告中的文件路径和行号,逐一修复兼容性问题。

5. 验证修复

# 重新扫描确认问题已修复
python check/scripts/mysql_to_dm_checker.py

# 对比修复前后的报告
diff check/reports/before_fix.json check/reports/after_fix.json

七、输出报告

JSON格式报告兼容性检测机器报告

{
  "scan_time": "2025-01-05T14:30:00",
  "summary": {
    "total_issues": 45,
    "by_severity": {
      "HIGH": 23,
      "MEDIUM": 15,
      "LOW": 7
    },
    "by_type": {
      "NOW": 18,
      "LIMIT": 12,
      "IF_FUNCTION": 8,
      "AUTO_INCREMENT": 5,
      "BOOLEAN": 2
    },
    "by_file": {
      "src/main/resources/mybatis/mapper/aiccs/abnormal/AbnormalListMapper.xml": 8,
      "src/main/java/com/chinaweal/aiccs/aiccs/seriousillegal/mapper/SeriousIllegalMapper.java": 5
    }
  },
  "issues": [
    {
      "issue_type": "NOW",
      "severity": "HIGH",
      "file_path": "src/main/resources/mybatis/mapper/aiccs/abnormal/AbnormalListMapper.xml",
      "line_number": 45,
      "mysql_pattern": "NOW()",
      "dm_pattern": "SYSDATE",
      "description": "达梦不支持NOW()函数",
      "suggestion": "将 NOW() 改为 SYSDATE"
    }
  ]
}

Markdown格式报告兼容性检测人工报告

参见工具生成的Markdown格式报告。


八、注意事项

  1. 数据库版本差异

    • 达梦8与达梦7语法有差异
    • 确认目标数据库版本
  2. 索引提示

    • 达梦不支持MySQL的索引提示语法
    • 删除后可能影响查询性能
  3. GROUP BY严格模式

    • 达梦要求SELECT中的非聚合字段必须在GROUP BY中
    • MySQL的宽松模式在达梦中会报错
  4. 字符串拼接

    • 达梦支持 ||CONCAT() 两种方式
    • 建议统一使用 CONCAT() 以提高代码可读性
  5. NULL处理

    • 达梦的NULL处理与MySQL可能有细微差异
    • 注意空字符串 ''NULL 的区别
  6. 自动生成的SQL

    • MyBatis-Plus等框架生成的SQL也需要检测
    • 可能需要配置框架的数据库方言

九、相关文件

  • Mapper XML路径: src/main/resources/mybatis/mapper/{数据源}/**/*.xml
  • 扫描脚本: check/scripts/scan_mysql_syntax.sh / scan_mysql_syntax.ps1
  • 检测工具: check/scripts/mysql_to_dm_checker.py
  • 检测报告: check/reports/compatibility_report.json / compatibility_report.md
  • 修复脚本: check/scripts/fix_compatibility.sh