MySQL 到 MariaDB SQL 文件转换工具

22 阅读9分钟

🎯 功能简介

这是一个通用的 Python 脚本,用于将 MySQL 导出的 SQL 文件转换为 MariaDB 兼容的格式。

✨ 功能特性

自动处理的兼容性问题

  1. ✅ 移除 GTID_PURGED 语句
    • MySQL 的全局事务标识符,MariaDB 不支持
  2. ✅ 移除 SQL_LOG_BIN 设置
    • MySQL 特有的二进制日志设置
  3. ✅ 转换排序规则(Collation)
    • utf8mb4_0900_ai_ciutf8mb4_unicode_ci
    • utf8mb4_0900_as_ciutf8mb4_unicode_ci
    • utf8mb4_0900_as_csutf8mb4_bin
    • utf8mb4_0900_binutf8mb4_bin
    • 其他 MySQL 8.0 特有的排序规则
  4. ✅ 移除不兼容的优化器提示
    • MySQL 8.0 的 /*+ ... */ 优化器提示
  5. ✅ 处理 INVISIBLE 列
    • MySQL 8.0 的隐藏列特性
  6. ✅ 处理版本特定的注释
    • MySQL 8.0 特有的条件注释

📋 系统要求

  • Python 3.6 或更高版本
  • Windows / Linux / macOS

🚀 使用方法

基本用法

# 自动生成输出文件名(输入文件名 + _mariadb 后缀)
python mysql_to_mariadb_converter.py input.sql

# 指定输出文件名
python mysql_to_mariadb_converter.py input.sql output.sql

示例

# 转换单个数据库
python mysql_to_mariadb_converter.py database.sql

# 输出:database_mariadb.sql

# 转换并指定输出文件名
python mysql_to_mariadb_converter.py mysql_backup.sql mariadb_import.sql

📊 转换统计

脚本会自动显示详细的转换统计信息:

Conversion Statistics:

  Total Lines:       195
  Removed Lines:     5
  Modified Lines:    39
  Output Lines:      190

Detailed Changes:

  - Removed GTID_PURGED:     1
  - Removed SQL_LOG_BIN:     3
  - Collation Changes:       39
  - Other Fixes:             0

🔍 转换示例

原始 MySQL SQL(不兼容)

-- GTID 设置
SET @@GLOBAL.GTID_PURGED='8559a6bb-978c-11f0-be00-fa163e8e9a1c:1-18264955';

-- SQL_LOG_BIN 设置
SET @@SESSION.SQL_LOG_BIN= 0;

-- MySQL 8.0 排序规则
CREATE TABLE `users` (
  `name` varchar(100) COLLATE utf8mb4_0900_ai_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

转换后的 MariaDB SQL(兼容)

-- 已移除 GTID_PURGED 和 SQL_LOG_BIN 语句

-- 转换为 MariaDB 兼容的排序规则
CREATE TABLE `users` (
  `name` varchar(100) COLLATE utf8mb4_unicode_ci NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

📝 完整工作流程

步骤 1:从 MySQL 导出数据

# 方法 A:使用 mysqldump(推荐)
mysqldump -u root -p --databases your_database > mysql_dump.sql

# 方法 B:DBeaver 导出
# 右键数据库 → 工具 → 备份数据库 → 选择保存位置

步骤 2:转换 SQL 文件

python mysql_to_mariadb_converter.py mysql_dump.sql

步骤 3:导入到 MariaDB

方法 A:命令行导入

# 创建数据库(如果不存在)
mysql -h 122.9.39.110 -u appuser -p -e "CREATE DATABASE IF NOT EXISTS your_database CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

# 导入数据
mysql -h 122.9.39.110 -u appuser -p your_database < mysql_dump_mariadb.sql

方法 B:DBeaver 导入

  1. 连接到 MariaDB 服务器
  2. 右键数据库 → SQL 编辑器打开 SQL 脚本
  3. 选择转换后的 _mariadb.sql 文件
  4. 点击执行(▶️ 按钮)

⚠️ 注意事项

1. 备份原始文件

转换前请务必备份原始 SQL 文件:

cp original.sql original.sql.backup

2. 数据类型兼容性

大多数数据类型是兼容的,但注意:

MySQL 8.0MariaDB说明
JSONJSONMariaDB 10.2+ 支持,但实现略有不同
INVISIBLE columns❌ 不支持脚本会自动移除
SPATIAL 数据类型✅ 支持通常兼容

3. 存储过程和函数

如果 SQL 包含存储过程或函数,可能需要手动检查:

  • MySQL 8.0 特有的语法可能不兼容
  • 建议导入后测试所有存储过程

4. 触发器和事件

触发器和事件通常兼容,但建议验证:

-- 查看触发器
SHOW TRIGGERS;

-- 查看事件
SHOW EVENTS;

🛠 高级用法

批量转换多个文件

Linux/Mac

#!/bin/bash
for file in *.sql; do
    python mysql_to_mariadb_converter.py "$file"
done

Windows PowerShell

Get-ChildItem *.sql | ForEach-Object {
    python mysql_to_mariadb_converter.py $_.Name
}

转换并直接导入

# 转换后立即导入
python mysql_to_mariadb_converter.py dump.sql && \
  mysql -h 122.9.39.110 -u appuser -p your_database < dump_mariadb.sql

🐛 常见问题

问题 1:字符编码错误

错误UnicodeDecodeError: 'utf-8' codec can't decode byte

解决:脚本已设置 errors='ignore',会自动跳过无效字符

问题 2:文件太大

问题:SQL 文件过大,内存不足

解决:拆分大文件

# Linux/Mac
split -l 10000 large_file.sql part_

# Windows - 使用 DBeaver 导出时选择"分卷"选项

问题 3:导入时仍然报错

可能原因

  • 存储过程包含不兼容的语法
  • 使用了 MySQL 8.0 特有的函数

解决

  1. 检查错误日志
  2. 手动修改不兼容的部分
  3. 逐表导入,定位问题表

📚 排序规则对照表

MySQL 8.0MariaDB说明
utf8mb4_0900_ai_ciutf8mb4_unicode_ci不区分重音,不区分大小写
utf8mb4_0900_as_ciutf8mb4_unicode_ci区分重音,不区分大小写
utf8mb4_0900_as_csutf8mb4_bin区分重音,区分大小写
utf8mb4_0900_binutf8mb4_bin二进制比较
utf8_0900_ai_ciutf8_unicode_ciUTF-8(3字节)版本

🔄 版本历史

v1.0.0 (2024-11-06)

  • ✅ 初始版本
  • ✅ 支持移除 GTID_PURGED
  • ✅ 支持移除 SQL_LOG_BIN
  • ✅ 支持排序规则转换
  • ✅ 支持 INVISIBLE 列处理
  • ✅ 详细的转换统计
  • ✅ Windows 兼容性

💡 最佳实践

1. 完整的迁移流程

# 1. 导出 MySQL 数据
mysqldump -u root -p --single-transaction --routines --triggers --events \
  --databases your_database > mysql_backup.sql

# 2. 转换为 MariaDB 格式
python mysql_to_mariadb_converter.py mysql_backup.sql

# 3. 在 MariaDB 上创建数据库
mysql -h mariadb_host -u appuser -p -e \
  "CREATE DATABASE your_database CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"

# 4. 导入数据
mysql -h mariadb_host -u appuser -p your_database < mysql_backup_mariadb.sql

# 5. 验证数据
mysql -h mariadb_host -u appuser -p your_database -e "SHOW TABLES;"

2. 数据验证

-- 比对表数量
SELECT COUNT(*) FROM information_schema.tables 
WHERE table_schema = 'your_database';

-- 比对行数
SELECT table_name, table_rows 
FROM information_schema.tables 
WHERE table_schema = 'your_database';

-- 检查字符集
SHOW VARIABLES LIKE 'character%';
SHOW VARIABLES LIKE 'collation%';

3. 性能测试

导入后建议进行性能测试:

-- 重建索引统计信息
ANALYZE TABLE table_name;

-- 优化表
OPTIMIZE TABLE table_name;

📞 技术支持

如果遇到问题:

  1. 查看转换统计:检查哪些内容被修改
  2. 对比转换前后:使用 diff 或文本编辑器对比
  3. 逐步导入:先导入表结构,再导入数据
  4. 查看日志:检查 MariaDB 错误日志

🎓 相关文档

📄 许可证

MIT License - 可自由使用和修改


完整脚本代码

将以下代码保存为 mysql_to_mariadb_converter.py,即可使用:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
MySQL 到 MariaDB SQL 文件转换工具

功能:
- 移除 GTID_PURGED 语句
- 移除 SQL_LOG_BIN 设置
- 转换 MySQL 8.0 特有的排序规则
- 移除不兼容的表空间引用
- 处理其他兼容性问题

使用方法:
    python3 mysql_to_mariadb_converter.py input.sql [output.sql]
    
示例:
    python3 mysql_to_mariadb_converter.py database.sql database_mariadb.sql
    python3 mysql_to_mariadb_converter.py database.sql  # 自动生成输出文件名
"""

import os
import sys
import re
from datetime import datetime

# 颜色输出
class Colors:
    GREEN = '\033[0;32m'
    YELLOW = '\033[1;33m'
    RED = '\033[0;31m'
    BLUE = '\033[0;34m'
    CYAN = '\033[0;36m'
    NC = '\033[0m'  # No Color
    BOLD = '\033[1m'

def print_success(message):
    """打印成功信息"""
    print(f"{Colors.GREEN}[OK] {message}{Colors.NC}")

def print_error(message):
    """打印错误信息"""
    print(f"{Colors.RED}[ERROR] {message}{Colors.NC}")

def print_warning(message):
    """打印警告信息"""
    print(f"{Colors.YELLOW}[WARNING] {message}{Colors.NC}")

def print_info(message):
    """打印信息"""
    print(f"{Colors.BLUE}[INFO] {message}{Colors.NC}")

def print_header():
    """打印脚本标题"""
    print(f"\n{Colors.CYAN}{'='*70}{Colors.NC}")
    print(f"{Colors.BOLD}{Colors.CYAN}  MySQL to MariaDB SQL Converter{Colors.NC}")
    print(f"{Colors.CYAN}{'='*70}{Colors.NC}\n")

class MySQLToMariaDBConverter:
    """MySQL 到 MariaDB 转换器"""
    
    def __init__(self):
        self.stats = {
            'total_lines': 0,
            'removed_lines': 0,
            'modified_lines': 0,
            'gtid_purged': 0,
            'sql_log_bin': 0,
            'collation_changes': 0,
            'other_changes': 0
        }
        
        # MySQL 8.0 排序规则映射到 MariaDB 兼容的排序规则
        self.collation_map = {
            'utf8mb4_0900_ai_ci': 'utf8mb4_unicode_ci',
            'utf8mb4_0900_as_ci': 'utf8mb4_unicode_ci',
            'utf8mb4_0900_as_cs': 'utf8mb4_bin',
            'utf8mb4_0900_bin': 'utf8mb4_bin',
            'utf8_0900_ai_ci': 'utf8_unicode_ci',
            'utf8_0900_as_ci': 'utf8_unicode_ci',
            'utf8_0900_as_cs': 'utf8_bin',
        }
    
    def should_remove_line(self, line):
        """判断是否应该移除该行"""
        line_stripped = line.strip()
        
        # 移除 GTID_PURGED 设置
        if 'GTID_PURGED' in line:
            self.stats['gtid_purged'] += 1
            self.stats['removed_lines'] += 1
            return True
        
        # 移除 SQL_LOG_BIN 设置
        if 'SQL_LOG_BIN' in line and ('SET' in line or 'SESSION' in line):
            self.stats['sql_log_bin'] += 1
            self.stats['removed_lines'] += 1
            return True
        
        # 移除 MYSQLDUMP_TEMP_LOG_BIN
        if 'MYSQLDUMP_TEMP_LOG_BIN' in line:
            self.stats['removed_lines'] += 1
            return True
        
        # 移除 GTID state 注释块
        if line_stripped.startswith('-- GTID state'):
            self.stats['removed_lines'] += 1
            return True
        
        return False
    
    def convert_collation(self, line):
        """转换 MySQL 8.0 特有的排序规则"""
        original_line = line
        
        # 替换排序规则
        for mysql_collation, mariadb_collation in self.collation_map.items():
            if mysql_collation in line:
                line = line.replace(mysql_collation, mariadb_collation)
                self.stats['collation_changes'] += 1
                self.stats['modified_lines'] += 1
        
        return line
    
    def process_line(self, line):
        """处理单行 SQL"""
        # 检查是否应该移除
        if self.should_remove_line(line):
            return None
        
        # 转换排序规则
        line = self.convert_collation(line)
        
        # 处理其他兼容性问题
        
        # 1. 移除 MySQL 8.0 特有的优化器提示
        if '/*+ ' in line:
            line = re.sub(r'/\*\+.*?\*/', '', line)
            self.stats['other_changes'] += 1
            self.stats['modified_lines'] += 1
        
        # 2. 转换 INVISIBLE 列(MySQL 8.0 特性)
        if ' INVISIBLE' in line and 'COLUMN' in line:
            line = line.replace(' INVISIBLE', '')
            self.stats['other_changes'] += 1
            self.stats['modified_lines'] += 1
        
        # 3. 处理不兼容的注释版本号
        # MariaDB 可能不识别 MySQL 8.0 的版本号
        line = re.sub(r'/\*!80\d{3}\s+(.*?)\s*\*/', r'\1', line)
        
        return line
    
    def convert_file(self, input_file, output_file):
        """转换 SQL 文件"""
        try:
            print_info(f"Reading file: {input_file}")
            
            # 检查输入文件是否存在
            if not os.path.exists(input_file):
                print_error(f"File not found: {input_file}")
                return False
            
            # 读取文件
            with open(input_file, 'r', encoding='utf-8', errors='ignore') as f:
                lines = f.readlines()
            
            self.stats['total_lines'] = len(lines)
            print_success(f"Read {self.stats['total_lines']} lines successfully")
            
            # 处理每一行
            print_info("Converting...")
            output_lines = []
            
            for line in lines:
                processed_line = self.process_line(line)
                if processed_line is not None:
                    output_lines.append(processed_line)
            
            # 在文件开头添加转换信息注释
            header_comment = f"""-- ============================================================
-- MySQL to MariaDB Conversion
-- Converted: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
-- Original file: {os.path.basename(input_file)}
-- Total lines: {self.stats['total_lines']}
-- Removed lines: {self.stats['removed_lines']}
-- Modified lines: {self.stats['modified_lines']}
-- ============================================================

"""
            output_lines.insert(0, header_comment)
            
            # 写入输出文件
            print_info(f"Writing file: {output_file}")
            with open(output_file, 'w', encoding='utf-8') as f:
                f.writelines(output_lines)
            
            print_success(f"Conversion complete! Output: {output_file}")
            return True
            
        except Exception as e:
            print_error(f"Conversion failed: {e}")
            import traceback
            traceback.print_exc()
            return False
    
    def print_stats(self):
        """打印转换统计信息"""
        print(f"\n{Colors.CYAN}{'='*70}{Colors.NC}")
        print(f"{Colors.BOLD}Conversion Statistics:{Colors.NC}\n")
        print(f"  Total Lines:       {Colors.YELLOW}{self.stats['total_lines']:,}{Colors.NC}")
        print(f"  Removed Lines:     {Colors.RED}{self.stats['removed_lines']:,}{Colors.NC}")
        print(f"  Modified Lines:    {Colors.BLUE}{self.stats['modified_lines']:,}{Colors.NC}")
        print(f"  Output Lines:      {Colors.GREEN}{self.stats['total_lines'] - self.stats['removed_lines']:,}{Colors.NC}")
        
        print(f"\n{Colors.BOLD}Detailed Changes:{Colors.NC}\n")
        print(f"  - Removed GTID_PURGED:     {self.stats['gtid_purged']}")
        print(f"  - Removed SQL_LOG_BIN:     {self.stats['sql_log_bin']}")
        print(f"  - Collation Changes:       {self.stats['collation_changes']}")
        print(f"  - Other Fixes:             {self.stats['other_changes']}")
        
        print(f"{Colors.CYAN}{'='*70}{Colors.NC}\n")

def main():
    """主函数"""
    print_header()
    
    # 解析命令行参数
    if len(sys.argv) < 2:
        print_error("Missing input file parameter!")
        print(f"\n{Colors.BOLD}Usage:{Colors.NC}")
        print(f"  {Colors.CYAN}python3 {sys.argv[0]} <input.sql> [output.sql]{Colors.NC}")
        print(f"\n{Colors.BOLD}Examples:{Colors.NC}")
        print(f"  {Colors.CYAN}python3 {sys.argv[0]} database.sql{Colors.NC}")
        print(f"  {Colors.CYAN}python3 {sys.argv[0]} database.sql database_mariadb.sql{Colors.NC}")
        sys.exit(1)
    
    input_file = sys.argv[1]
    
    # 确定输出文件名
    if len(sys.argv) > 2:
        output_file = sys.argv[2]
    else:
        # 自动生成输出文件名
        base_name = os.path.splitext(input_file)[0]
        extension = os.path.splitext(input_file)[1]
        output_file = f"{base_name}_mariadb{extension}"
    
    print_info(f"Input file: {input_file}")
    print_info(f"Output file: {output_file}")
    
    # 检查输出文件是否存在
    if os.path.exists(output_file):
        print_warning(f"Output file exists: {output_file}")
        response = input("Overwrite? (y/N): ")
        if response.lower() != 'y':
            print_info("Conversion cancelled")
            sys.exit(0)
    
    # 创建转换器并执行转换
    converter = MySQLToMariaDBConverter()
    
    if converter.convert_file(input_file, output_file):
        converter.print_stats()
        
        # 打印后续步骤
        print(f"{Colors.BOLD}Next Steps:{Colors.NC}\n")
        print(f"1. Verify the converted SQL file")
        print(f"2. Import to MariaDB:")
        print(f"   {Colors.CYAN}mysql -h <host> -u <user> -p <database> < {output_file}{Colors.NC}")
        print(f"3. Or use DBeaver GUI to import\n")
        
        print_success("Conversion completed successfully!")
    else:
        print_error("Conversion failed!")
        sys.exit(1)

if __name__ == "__main__":
    main()

如何使用

  1. 复制上面的代码,保存为 mysql_to_mariadb_converter.py

  2. 运行转换

    python mysql_to_mariadb_converter.py your_database.sql
    
  3. 导入到 MariaDB

    mysql -h your_host -u your_user -p your_database < your_database_mariadb.sql
    

祝您迁移顺利! 🎉