将 MySQL-PXC 迁移到 MySQL-MGR

本指南提供了在 Alauda Container Platform 上从 MySQL-PXC(Percona XtraDB Cluster)5.7 迁移到 MySQL Group Replication(MGR)8.0 的完整操作说明。

背景

挑战

MySQL 5.7 已于 2023 年 10 月达到生命周期结束(EOL),而 MySQL-PXC 已从 Alauda Database Service for MySQL v4.3.0 开始弃用并移除。组织必须迁移到 MySQL 8.0,才能继续获取安全更新并利用新特性。

迁移生产数据库涉及复杂的考量:

  • 与 MySQL 8.0 保留关键字的 Schema 兼容性
  • 字符集变更(utf8mb4)
  • 认证插件更新
  • 在迁移过程中确保数据完整性

解决方案

本指南提供了将 MySQL-PXC 5.7 迁移到 MySQL-MGR 8.0 的完整、已验证操作说明:

  • 经过验证的方法:已在 ACP v4.0+ 上使用 Alauda Database Service for MySQL 验证
  • 完整对象覆盖:迁移所有标准 MySQL 对象(tables、views、routines、triggers、events、users、grants)
  • Schema 兼容性:自动检查并修复 MySQL 8.0 兼容性问题
  • 全面验证:覆盖 9 类对象的验证
  • 风险最小化:提供详细的回滚操作步骤,并在每个步骤进行校验

环境信息

组件版本
源端(PXC)Percona XtraDB Cluster 5.7.44
目标端(MGR)MySQL Group Replication 8.0.44
ACP 版本v4.0 或更高
MySQL Operatorv4.0 或更高

PXC 与 MGR:主要差异

方面PXC 5.7(源端)MGR 8.0(目标端)
Pod 名称模式${NAME}-pxc-0${NAME}-0
Container 标识符不需要(默认是 mysql)必需:-c mysql
主端点${NAME}-proxysql.${NS}.svc.cluster.local:3306${NAME}-read-write.${NS}.svc.cluster.local:3306
从端点与主端点相同(由 ProxySQL 负责路由)${NAME}-read-only.${NS}.svc.cluster.local:3306
复制类型Galera(同步多主)Group Replication(单主模式,带异步从节点)
Secret 名称模式${NAME}mgr-${NAME}-password
WARNING

在运行迁移命令之前,请始终先使用 kubectl get pod -n <namespace> 检查实际的 Pod 名称。

常见使用场景

场景数据库大小预计停机时间
小型数据库< 10GB15-30 分钟
中型数据库10-50GB30-60 分钟
大型数据库50-200GB1-2 小时
Schema 问题任意大小+1-2 小时用于修复
字符集迁移任意大小+30-60 分钟

先决条件

在执行迁移前,请确保满足以下条件:

  • 已安装 ACP v4.0 或更高版本,以及 MySQL Operator v4.0 或更高版本
  • 源端为运行正常的 MySQL-PXC 5.7 集群
  • 已启用 GTID 模式(@@gtid_mode = ON@@enforce_gtid_consistency = ON
  • 已在迁移前创建新的 MySQL-MGR 8.0 目标集群
  • 存储容量为源数据库大小的 2-3 倍
  • 本地机器可同时访问两个集群的网络连通性

重要限制

  • 为保证一致性,在导出和导入期间需要应用停机
  • 推荐的最大数据库大小:200GB(更大的数据库可能需要其他迁移方案)
  • 源集群必须启用 GTID
  • 必须在迁移开始前创建目标集群
  • 目标端的存储性能应与源端相当或更高

开始之前

1. 获取 MySQL Root 密码

# For PXC 5.7 source
kubectl get secret <source-name> -n <source-namespace> -o jsonpath='{.data.root}' | base64 -d

# For MGR 8.0 target
kubectl get secret mgr-<target-name>-password -n <target-namespace> -o jsonpath='{.data.root}' | base64 -d

2. 识别 Pod 名称

# Check source PXC pods
kubectl get pod -n <source-namespace> | grep <source-name>
# Example output: source-pxc-0, source-pxc-1, source-pxc-2

# Check target MGR pods
kubectl get pod -n <target-namespace> | grep <target-name>
# Example output: target-0, target-1, target-2

3. 验证集群状态

# Check PXC source status
kubectl get mysql <source-name> -n <source-namespace>
# Expected: STATE = ready, PXCSTATE = ready

# Check MGR target status
kubectl get mysql <target-name> -n <target-namespace>
# Expected: All 3 members ready, STATUS = Running

4. kubectl Exec 最佳实践

适用于 PXC 5.7(源端):

# No container specifier needed for PXC
kubectl exec <source-name>-pxc-0 -n <namespace> -- \
  mysql -uroot -p<password> -e "SQL_HERE"

适用于 MGR 8.0(目标端):

# Always use -c mysql for MGR
kubectl exec <target-name>-0 -n <namespace> -c mysql -- \
  mysql -uroot -p<password> -e "SQL_HERE"
TIP
  • 始终使用如下参数顺序:kubectl exec -n <namespace> <pod-name> -- <command>
  • 在命令前使用 --(双连字符),用于分隔 kubectl 选项和命令
  • 避免在 kubectl exec 中使用 heredoc(<<EOF)——它们通常会因 shell 引号处理问题而失败

执行指南

步骤 1:Schema 兼容性分析

请在计划迁移前 一周 执行此分析。

运行以下命令以检测 Schema 兼容性问题:

# Check for reserved keyword columns
kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
  mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -e "
    SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
    FROM information_schema.COLUMNS
    WHERE LOWER(COLUMN_NAME) IN ('rank','groups','function','tables','indexes','procedure','file','process');
  "

# Check for ZEROFILL columns
kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
  mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -e "
    SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLUMN_TYPE
    FROM information_schema.COLUMNS
    WHERE COLUMN_TYPE LIKE '%ZEROFILL%';
  "

# Check for invalid date defaults
kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
  mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -e "
    SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, COLUMN_DEFAULT
    FROM information_schema.COLUMNS
    WHERE COLUMN_DEFAULT = '0000-00-00'
       OR COLUMN_DEFAULT = '0000-00-00 00:00:00';
  "

修复 Schema 问题

# Fix reserved keyword columns (example)
kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
  mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -e "
    USE db1;
    ALTER TABLE users CHANGE COLUMN rank user_rank INT;
  "

# Fix ZEROFILL columns
kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
  mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -e "
    USE db1;
    ALTER TABLE products MODIFY COLUMN price DECIMAL(10,2);
  "

步骤 2:字符集和排序规则分析

检查非 utf8mb4 的表:

kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
  mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -e "
    SELECT TABLE_SCHEMA, TABLE_NAME, TABLE_COLLATION
    FROM information_schema.TABLES
    WHERE TABLE_SCHEMA NOT IN ('information_schema','mysql','performance_schema','sys')
      AND TABLE_COLLATION NOT LIKE 'utf8mb4%';
  "

转换为 utf8mb4

# Convert databases to utf8mb4
for db in ${DATABASES}; do
  kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
    mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -e "
      ALTER DATABASE ${db} CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
    "
done

# Convert tables to utf8mb4
for db in ${DATABASES}; do
  TABLES=$(kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
    mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -N -e "
      SELECT TABLE_NAME FROM information_schema.TABLES
      WHERE TABLE_SCHEMA = '${db}' AND TABLE_TYPE = 'BASE TABLE';
    ")

  for table in ${TABLES}; do
    echo "Converting ${db}.${table}..."
    kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
      mysql -uroot -p${SOURCE_MYSQL_PASSWORD} ${db} -e "
        ALTER TABLE ${table} CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
      "
  done
done
WARNING

对于包含较长 VARCHAR/TEXT 索引(>191 字符)的表,可能需要调整索引长度:

ALTER TABLE users DROP INDEX idx_email;
ALTER TABLE users ADD UNIQUE INDEX idx_email (email(191));

步骤 3:创建目标 MySQL-MGR 8.0 实例

请在数据迁移阶段 开始前不久 创建目标 MySQL-MGR 8.0 实例。

使用 Web 控制台:

  1. 选择 MySQL 版本 8.0
  2. 配置资源(由于 MySQL 8.0 的额外开销,建议内存比源集群 增加 10-20%
  3. 将存储大小设置为源数据库大小的 2-3 倍

使用命令行:

TARGET_NAME="mysql-8-target"
NAMESPACE="your-namespace"
STORAGE_SIZE="500Gi"

cat << EOF | kubectl -n $NAMESPACE apply -f -
apiVersion: middleware.alauda.io/v1
kind: Mysql
metadata:
  name: $TARGET_NAME
  namespace: $NAMESPACE
  labels:
    mysql/arch: mgr
spec:
  mgr:
    enableStorage: true
    image: {}
    members: 1
    monitor:
      enable: true
      exporter: {}
    resources:
      server:
        limits:
          cpu: "2"
          memory: 4Gi
        requests:
          cpu: "2"
          memory: 4Gi
    router:
      replicas: 1
      resources:
        limits:
          cpu: 500m
          memory: 512Mi
        requests:
          cpu: 500m
          memory: 512Mi
      svcRO:
        type: ClusterIP
      svcRW:
        type: ClusterIP
    strictSecurityModeEnabled: true
    upgradeOption: {}
    volumeClaimTemplate:
      metadata: {}
      spec:
        accessModes:
        - ReadWriteOnce
        resources:
          requests:
            storage: ${STORAGE_SIZE}
        storageClassName: dataservice-topolvmsc
      status: {}
  params:
    mysql: {}
    router:
      DEFAULT:
        max_total_connections: "200"
      logger:
        level: info
  upgradeOption:
    autoUpgrade: false
    crVersion: 4.2.0
  version: "8.0"
EOF

验证目标集群:

kubectl -n $NAMESPACE get mysql $TARGET_NAME -w
# Expected: STATE = Ready, MGRSTATE = ready

步骤 4:迁移数据、用户和权限

关键:停止应用写入

从此时开始,应用必须保持停止状态(或严格只读),直到切换完成。此步骤之后写入源数据库的任何数据都将丢失。

操作步骤:

  1. 停止应用写入:将应用副本数缩容到 0:

    kubectl scale deployment <app-name> --replicas=0 -n <app-namespace>
  2. 识别要迁移的数据库

    # List user databases (DO NOT include: information_schema, mysql, performance_schema, sys)
    kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
      mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -N -e "
        SELECT SCHEMA_NAME FROM information_schema.SCHEMATA
        WHERE SCHEMA_NAME NOT IN ('information_schema','mysql','performance_schema','sys');
      "
  3. 导出并导入数据

    SOURCE_NAME="source"
    SOURCE_NAMESPACE="your-namespace"
    SOURCE_MYSQL_PASSWORD="source-root-password"
    
    TARGET_NAME="mysql-8-target"
    TARGET_NAMESPACE="your-namespace"
    TARGET_MYSQL_PASSWORD="target-root-password"
    
    DATABASES="db1 db2 db3"
    
    for db in ${DATABASES}; do
      echo "Migrating database: ${db}..."
    
      # Export from PXC source
      kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
        mysqldump -uroot -p${SOURCE_MYSQL_PASSWORD} \
          --single-transaction \
          --quick \
          --lock-tables=false \
          --set-gtid-purged=ON \
          --routines \
          --events \
          --triggers \
          --databases ${db} \
        | grep -v "SET @@GLOBAL.GTID_PURGED" \
        | kubectl exec -i ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
          mysql -uroot -p${TARGET_MYSQL_PASSWORD}
    
      echo "Migrated: ${db}"
    done
  4. 迁移用户和权限

    # List non-system users from PXC source
    USERS=$(kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
      mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -N -e "
        SELECT CONCAT('''', User, '''@''', Host, '''')
        FROM mysql.user
        WHERE User NOT IN ('root','mysql.sys','mysql.session','mysql.infoschema',
                           'monitor','operator','clustercheck','xtrabackup','replication')
          AND User NOT LIKE 'mysql.%'
          AND Host != 'localhost';
      ")
    
    # For each user, create user and apply grants on target
    for user_host in ${USERS}; do
      echo "Migrating user: ${user_host}..."
    
      # Get CREATE USER statement (includes auth plugin and password hash)
      CREATEUSER=$(kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
        mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -N -e "SHOW CREATE USER ${user_host};")
    
      # Create user on target
      kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
        mysql -uroot -p${TARGET_MYSQL_PASSWORD} -e "${CREATEUSER};"
    
      # Get GRANT statements from source
      GRANTS=$(kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
        mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -N -e "SHOW GRANTS FOR ${user_host};")
    
      # Apply grants on target
      echo "${GRANTS}" | while read grant; do
        kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
          mysql -uroot -p${TARGET_MYSQL_PASSWORD} -e "${grant};"
      done
    done
    
    # Flush privileges on target
    kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
      mysql -uroot -p${TARGET_MYSQL_PASSWORD} -e "FLUSH PRIVILEGES;"
    TIP

    在 MySQL 8.0 目标端,SHOW CREATE USER 会保留原始认证插件和密码哈希,因此用户在迁移后仍可使用现有密码登录。如果需要为了客户端兼容性切换到 mysql_native_password,请运行:

    kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
      mysql -uroot -p${TARGET_MYSQL_PASSWORD} -e "
        ALTER USER ${user_host} IDENTIFIED WITH mysql_native_password BY '<password>';
      "
    MySQL 8.4 目标端

    在 MySQL 8.4 目标端,mysql_native_password 插件 默认未加载且无法启用,因此重新应用使用 mysql_native_password 的 5.7/8.0 SHOW CREATE USER 定义会失败,并报错 ERROR 1524 (HY000): Plugin 'mysql_native_password' is not loaded。对于 8.4 目标端,请使用默认的 caching_sha2_password 插件重新创建每个用户,而不是保留原始哈希:

    kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
      mysql -uroot -p${TARGET_MYSQL_PASSWORD} -e "
        CREATE USER ${user_host} IDENTIFIED BY '<password>';
      "
    # then re-apply the user's GRANTs (collected above)

    这要求你知道或重置每个用户的密码——mysql_native_password 的哈希无法直接迁移为 caching_sha2_password。请确保应用驱动/连接字符串支持 caching_sha2_password

TIP

上面的 mysqldump 流式方式不需要为 dump 文件预留磁盘空间。

步骤 5:验证迁移

TARGET_NAME="mysql-8-target"
TARGET_NAMESPACE="your-namespace"
TARGET_MYSQL_PASSWORD="target-root-password"
DATABASES="db1 db2 db3"

for db in ${DATABASES}; do
  echo "=== Verifying database: ${db} ==="

  # Compare table counts
  echo "Tables:"
  kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
    mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -N -e "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = '${db}' AND TABLE_TYPE = 'BASE TABLE';"

  kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
    mysql -uroot -p${TARGET_MYSQL_PASSWORD} -N -e "SELECT COUNT(*) FROM information_schema.TABLES WHERE TABLE_SCHEMA = '${db}' AND TABLE_TYPE = 'BASE TABLE';"

  # Verify views execute correctly
  kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
    mysql -uroot -p${TARGET_MYSQL_PASSWORD} -N -e "
      SELECT TABLE_NAME FROM information_schema.VIEWS WHERE TABLE_SCHEMA = '${db}';
    " | while read view; do
      kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
        mysql -uroot -p${TARGET_MYSQL_PASSWORD} ${db} -e "SELECT 1 FROM ${view} LIMIT 1;" > /dev/null 2>&1 \
        && echo "✓ View ${view} OK" \
        || echo "✗ View ${view} FAILED"
    done
done

验证清单:

  • 每个数据库中的表数量相同
  • 每个表的行数相同
  • 视图数量相同
  • 所有视图都能成功执行
  • 所有存储过程和函数都存在
  • 所有触发器和事件都已迁移
  • 所有用户账户都存在
  • 所有权限都已迁移

步骤 6:迁移后优化

1. 更新表统计信息

for db in ${DATABASES}; do
  echo "Analyzing tables in ${db}..."
  TABLES=$(kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
    mysql -uroot -p${TARGET_MYSQL_PASSWORD} -N -e "
      SELECT TABLE_NAME FROM information_schema.TABLES
      WHERE TABLE_SCHEMA = '${db}' AND TABLE_TYPE = 'BASE TABLE';
    ")

  for table in ${TABLES}; do
    kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
      mysql -uroot -p${TARGET_MYSQL_PASSWORD} ${db} -e "ANALYZE TABLE ${table};" 2>&1 | grep -v "Table"
  done
done

2. 检查碎片

# Build properly-quoted comma-separated list
DB_LIST=$(echo $DATABASES | sed "s/ /','/g")
DB_LIST="'$DB_LIST'"

kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
  mysql -uroot -p${TARGET_MYSQL_PASSWORD} -e "
    SELECT TABLE_SCHEMA, TABLE_NAME,
           ROUND(DATA_FREE / 1024 / 1024, 2) AS 'Fragmentation (MB)'
    FROM information_schema.TABLES
    WHERE TABLE_SCHEMA IN ($DB_LIST)
      AND DATA_FREE > 0
    ORDER BY DATA_FREE DESC;
  "

如果发现明显碎片(>100MB),请重建表:

OPTIMIZE TABLE db1.orders;

原生应用切换

步骤 7:将原生应用切换到目标端

1. 验证应用已停止

# Ensure application is scaled down
kubectl scale deployment <app-name> --replicas=0 -n <app-namespace>

# Verify no active connections on source
kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
  mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -e "SHOW PROCESSLIST;" | grep -v "Sleep"

2. 更新应用连接字符串

# Update ConfigMap or environment variables
kubectl patch configmap <app-config> -n <app-namespace> --type=json \
  -p='[{"op": "replace", "path": "/data/database-host", "value":"mysql-8-target-read-write.'${TARGET_NAMESPACE}'.svc.cluster.local"}]'

kubectl patch configmap <app-config> -n <app-namespace> --type=json \
  -p='[{"op": "replace", "path": "/data/database-port", "value":"3306"}]'

3. 重启应用

# Scale up application
kubectl scale deployment <app-name> --replicas=<original-replica-count> -n <app-namespace>

# Wait for pods to be ready
kubectl -n <app-namespace> rollout status deployment <app-name>

4. 验证应用功能

# Test database connectivity
kubectl exec <app-pod> -n <app-namespace> -- \
  mysql -h mysql-8-target-read-write.${TARGET_NAMESPACE}.svc.cluster.local \
    -uroot -p${TARGET_MYSQL_PASSWORD} -e "SELECT 1 AS test;"

# Check application logs for errors
kubectl logs -n <app-namespace> <app-pod> --tail=100 | grep -i error

监控

对迁移后的实例进行 24-48 小时监控:

# Check MySQL 8.0 instance health
kubectl -n ${TARGET_NAMESPACE} get mysql ${TARGET_NAME} -w

# Monitor error logs
kubectl logs -n ${TARGET_NAMESPACE} ${TARGET_NAME}-0 -c mysql --tail=100 -f

灾难恢复

回滚计划

如果在切换后发现严重问题:

# 1. Stop application
kubectl scale deployment <app-name> --replicas=0 -n <app-namespace>

# 2. Update connection string back to source
kubectl patch configmap <app-config> -n <app-namespace> --type=json \
  -p='[{"op": "replace", "path": "/data/database-host", "value":"'${SOURCE_NAME}'-proxysql.'${SOURCE_NAMESPACE}'.svc.cluster.local"}]'

# 3. Restart application
kubectl scale deployment <app-name> --replicas=<original-replica-count> -n <app-namespace>

# 4. Verify connectivity
kubectl exec <app-pod> -n <app-namespace> -- \
  mysql -h ${SOURCE_NAME}-proxysql.${SOURCE_NAMESPACE}.svc.cluster.local \
    -uroot -p${SOURCE_MYSQL_PASSWORD} -e "SELECT 1 AS test;"
WARNING

在迁移得到验证且回滚窗口结束之前,不要删除 PXC 自定义资源。

常见问题及解决方法

问题:GTID_PURGED 错误

症状:

ERROR 3546 (HY000) at line XX: Cannot update GTID_PURGED with the Group Replication plugin running

解决方法: 已在迁移操作步骤中通过过滤 grep -v "SET @@GLOBAL.GTID_PURGED" 处理。

问题:字符集转换错误

症状:

ERROR 1366 (HY000): Incorrect string value

解决方法:

kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
  mysql -uroot -p${TARGET_MYSQL_PASSWORD} -e "
    ALTER DATABASE ${db} CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
  "

问题:DEFINER 权限错误

症状:

ERROR 1449 (HY000): The user specified as a definer ('user'@'host') does not exist

解决方法:

# Find all objects with missing definers
kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
  mysql -uroot -p${TARGET_MYSQL_PASSWORD} -e "
    SELECT DISTINCT DEFINER
    FROM information_schema.VIEWS
    WHERE TABLE_SCHEMA = '${db}';
  "

问题:认证插件错误

症状:

ERROR 2059 (HY000): Authentication plugin 'caching_sha2_password' cannot be loaded

解决方法(MySQL 8.0 目标端): 对于无法协商 caching_sha2_password 的旧客户端,将用户切换为 mysql_native_password

kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
  mysql -uroot -p${TARGET_MYSQL_PASSWORD} -e "
    ALTER USER 'app_user'@'%' IDENTIFIED WITH mysql_native_password BY 'password';
    FLUSH PRIVILEGES;
  "

解决方法(MySQL 8.4 目标端): 在 8.4 中,mysql_native_password 不可用(ERROR 1524: Plugin 'mysql_native_password' is not loaded),因此不能回退到该插件。请改为升级客户端驱动/连接器到支持 caching_sha2_password 的版本(并使用 TLS,因为 caching_sha2_password 对非本地连接的首次密码交换需要 TLS)。保持用户使用默认的 caching_sha2_password 插件即可。

故障排除

诊断命令

检查迁移进度:

kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
  mysql -uroot -p${TARGET_MYSQL_PASSWORD} -e "SHOW PROCESSLIST;"

kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
  mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -e "SHOW PROCESSLIST;"

验证数据完整性:

for db in ${DATABASES}; do
  echo "=== Database: ${db} ==="
  kubectl exec ${SOURCE_NAME}-pxc-0 -n ${SOURCE_NAMESPACE} -- \
    mysql -uroot -p${SOURCE_MYSQL_PASSWORD} -N -e "
      SELECT TABLE_NAME, TABLE_ROWS
      FROM information_schema.TABLES
      WHERE TABLE_SCHEMA = '${db}' AND TABLE_TYPE = 'BASE TABLE'
      ORDER BY TABLE_NAME;
    " > /tmp/source_counts.txt

  kubectl exec ${TARGET_NAME}-0 -n ${TARGET_NAMESPACE} -c mysql -- \
    mysql -uroot -p${TARGET_MYSQL_PASSWORD} -N -e "
      SELECT TABLE_NAME, TABLE_ROWS
      FROM information_schema.TABLES
      WHERE TABLE_SCHEMA = '${db}' AND TABLE_TYPE = 'BASE TABLE'
      ORDER BY TABLE_NAME;
    " > /tmp/target_counts.txt

  diff /tmp/source_counts.txt /tmp/target_counts.txt || echo "Row count differences detected!"
done

检查 MySQL 8.0 错误日志:

kubectl logs -n ${TARGET_NAMESPACE} ${TARGET_NAME}-0 -c mysql --tail=100 -f | grep -i error

最佳实践

迁移前规划

  • 在测试环境中验证:始终先在非生产环境执行测试迁移
  • Schema 清理:在生产迁移前修复所有 Schema 兼容性问题
  • 字符集迁移:尽早转换为 utf8mb4(建议在迁移前 3-5 天完成)
  • 备份策略:确保迁移前有最新备份可用
  • 维护窗口:根据数据库大小安排充足的停机时间

迁移期间

  • 停止应用写入:确保导出/导入期间没有写入,以保持一致性
  • 监控进度:定期跟踪导出/导入进度
  • 保留源端运行:在迁移验证完成前不要删除源端

迁移后

  • 全面测试:彻底测试应用功能
  • 性能监控:持续监控查询性能 24-48 小时
  • 保留源端用于回滚:在回滚窗口内保留源集群 24-48 小时
  • 更新文档:更新连接字符串和监控面板

参考

大小与时间估算

数据库大小导出时间导入时间总停机时间
< 10GB1-5 分钟2-10 分钟15-30 分钟
10-50GB5-20 分钟10-30 分钟30-60 分钟
50-100GB20-40 分钟30-60 分钟1-2 小时
100-200GB40-80 分钟1-2 小时2-4 小时

mysqldump 参数参考

参数作用
--single-transaction使用 MVCC(InnoDB)获取一致性快照
--quick一次获取一行(内存效率高)
--lock-tables=false不锁表(依赖 single-transaction)
--set-gtid-purged=ON包含 GTID 信息
--routines导出存储过程和函数
--events导出事件
--triggers导出触发器
--databases指定要导出的数据库

有用链接