24 KiB
24 KiB
跨数据库SQL兼容性检测方案
一、检测目标
检测系统中所有SQL语句在目标数据库(达梦/MySQL/Oracle/DB2)上的兼容性,识别并修复不兼容的SQL语法,确保系统平滑迁移到新数据库环境。
二、检测范围
检测对象
- Mapper XML文件:所有MyBatis Mapper XML中的SQL语句
- Java代码:使用注解(@Select/@Insert/@Update/@Delete)的SQL
- 动态SQL:Java代码中拼接的SQL字符串
- 数据库脚本:DDL/DML脚本文件
检测项
-
SQL语法兼容性
- MySQL特有语法 → 达梦/Oracle语法
- 分页、函数、操作符等
-
数据类型兼容性
- MySQL特有类型 → 标准SQL类型
- 类型映射关系
-
字符串和日期处理
- 字符串拼接、日期函数等
三、MySQL到达梦兼容性对照表
| SQL类型 | MySQL语法 | 达梦语法 | 兼容性说明 | 检测方法 |
|---|---|---|---|---|
| 分页 | LIMIT 10, 20 |
LIMIT 20 OFFSET 10 或 ROWNUM |
达梦8+支持LIMIT | Grep搜索LIMIT |
| 自增主键 | AUTO_INCREMENT |
IDENTITY 或 SEQUENCE |
需修改为IDENTITY | Grep搜索AUTO_INCREMENT |
| 日期函数 | NOW() |
SYSDATE |
需替换为SYSDATE | Grep搜索NOW() |
| 字符串拼接 | CONCAT(a,b) 或 CONCAT_WS() |
a || b 或 CONCAT() |
达梦支持两种方式 | 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
方案B:Python自动化检测工具
#!/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 改为 1,FALSE 改为 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 <= NOW()
</select>
达梦写法:
<select id="selectByCreateTime" resultType="...">
SELECT * FROM abnormal_list
WHERE create_time <= 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. 分析报告
根据报告中的问题优先级处理:
-
HIGH(高危)
- 影响SQL执行的关键语法
- 必须修复才能正常运行
-
MEDIUM(中危)
- 可能有兼容性问题
- 建议修复
-
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格式报告。
八、注意事项
-
数据库版本差异
- 达梦8与达梦7语法有差异
- 确认目标数据库版本
-
索引提示
- 达梦不支持MySQL的索引提示语法
- 删除后可能影响查询性能
-
GROUP BY严格模式
- 达梦要求SELECT中的非聚合字段必须在GROUP BY中
- MySQL的宽松模式在达梦中会报错
-
字符串拼接
- 达梦支持
||和CONCAT()两种方式 - 建议统一使用
CONCAT()以提高代码可读性
- 达梦支持
-
NULL处理
- 达梦的NULL处理与MySQL可能有细微差异
- 注意空字符串
''和NULL的区别
-
自动生成的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