1. MySQL到openGauss迁移的核心挑战
最近在帮客户做数据库迁移时,踩了不少坑。从MySQL切换到openGauss看似只是换个数据库,实际暗藏两大"杀手级"差异:大小写敏感问题和二进制类型处理。这两个问题如果没处理好,轻则查询报错,重则数据丢失。
先说说大小写敏感这个坑。MySQL默认是大小写不敏感的,SELECT * FROM users WHERE name='Tom'和SELECT * FROM users WHERE name='tom'返回结果相同。但openGauss默认是大小写敏感的,同样的查询可能返回空结果。这个问题在应用代码里埋得最深——很多开发者根本意识不到自己依赖了这个特性。
二进制类型的问题更隐蔽。MySQL的BLOB类型在openGauss里对应bytea类型,但JDBC驱动处理方式完全不同。我见过最坑的情况是:数据能正常写入,但读取时却报错,原因竟是MyBatis映射配置多了个jdbcType属性。
2. 大小写敏感问题的终极解决方案
2.1 兼容性模式的选择
openGauss支持四种兼容模式:
- A模式:兼容Oracle
- B模式:兼容MySQL(推荐)
- C模式:兼容Teradata
- PG模式:兼容PostgreSQL
关键操作:创建数据库时指定B模式
CREATE DATABASE mydb DBCOMPATIBILITY 'B';2.2 字符序(Collation)的魔法
在B模式下,通过字符序控制大小写敏感:
utf8mb4_general_ci:不区分大小写utf8mb4_bin:区分大小写
建表示例:
CREATE TABLE users ( id VARCHAR(32) COLLATE "pg_catalog"."utf8mb4_general_ci" PRIMARY KEY, username VARCHAR(100) COLLATE "pg_catalog"."utf8mb4_general_ci", password VARCHAR(100) COLLATE "pg_catalog"."utf8mb4_bin" );2.3 实际踩坑案例
我们迁移的一个用户表有50万条数据,原MySQL查询:
SELECT * FROM users WHERE email='USER@EXAMPLE.COM';在openGauss中必须改为:
-- 方案1:修改查询条件(不推荐) SELECT * FROM users WHERE LOWER(email)=LOWER('USER@EXAMPLE.COM'); -- 方案2:建表时指定字符序(推荐) CREATE TABLE users ( email VARCHAR(255) COLLATE "pg_catalog"."utf8mb4_general_ci" );3. 二进制数据类型迁移指南
3.1 类型映射关系
| MySQL类型 | openGauss类型 | 注意事项 |
|---|---|---|
| BLOB | bytea | 首选方案 |
| BLOB | blob | 不推荐使用 |
3.2 JDBC操作对比
MySQL写法:
// 插入BLOB数据 preparedStatement.setBlob(1, inputStream); // MyBatis映射 @Result(column="file_data", property="fileData", jdbcType=JdbcType.BLOB)openGauss正确写法:
// 插入bytea数据 preparedStatement.setBinaryStream(1, inputStream); // 或 preparedStatement.setBytes(1, bytes); // MyBatis映射(去掉jdbcType!) @Result(column="file_data", property="fileData")3.3 性能优化技巧
处理大二进制数据时:
- 增大JDBC连接参数:
defaultRowFetchSize=100 - 使用流式读取:
try (ResultSet rs = statement.executeQuery()) { while (rs.next()) { try (InputStream is = rs.getBinaryStream("data")) { // 处理流数据 } } }
4. 完整迁移流程
4.1 准备工作
安装openGauss Docker镜像:
docker run --name opengauss \ -e GS_PASSWORD=My@ComplexPassword123 \ -p 5432:5432 \ -d enmotech/opengauss:5.0.1创建目标模式:
CREATE SCHEMA myapp; SET search_path TO myapp;
4.2 SQL脚本转换
使用sed命令批量处理:
# 1. 移除所有双引号 sed 's/"//g' mysql_dump.sql > temp.sql # 2. 转换BLOB类型 sed -i 's/BLOB/bytea/g' temp.sql # 3. 处理保留字字段(如order改为"order") sed -i 's/`order`/"order"/g' temp.sql4.3 迁移验证清单
检查所有表数量是否一致:
SELECT count(*) FROM information_schema.tables WHERE table_schema='myapp';验证数据量:
-- MySQL SELECT table_name, table_rows FROM information_schema.tables WHERE table_schema='mydb'; -- openGauss SELECT relname AS table_name, n_live_tup AS row_count FROM pg_stat_user_tables WHERE schemaname='myapp';
5. 应用层适配要点
5.1 MyBatis配置调整
错误示例:
<resultMap id="userResult" type="User"> <result property="avatar" column="avatar" jdbcType="BLOB"/> </resultMap>正确写法:
<resultMap id="userResult" type="User"> <result property="avatar" column="avatar"/> </resultMap>5.2 Spring Boot配置
spring: datasource: driver-class-name: org.postgresql.Driver url: jdbc:postgresql://localhost:5432/mydb?currentSchema=myapp username: myuser password: mypassword hikari: connection-init-sql: SET search_path TO myapp5.3 事务处理差异
MySQL默认RR隔离级别,openGauss默认RC。需要显式设置:
@Transactional(isolation = Isolation.REPEATABLE_READ) public void transferMoney() { // 业务逻辑 }6. 性能调优建议
连接池配置:
spring: datasource: hikari: maximum-pool-size: 20 idle-timeout: 30000 max-lifetime: 1800000关键参数调整:
-- 增加工作内存 ALTER SYSTEM SET work_mem = '16MB'; -- 优化并行查询 ALTER SYSTEM SET max_parallel_workers = 8; ALTER SYSTEM SET max_parallel_workers_per_gather = 4;监控指标:
-- 查看慢查询 SELECT * FROM pg_stat_statements ORDER BY total_time DESC LIMIT 10; -- 锁等待监控 SELECT blocked_locks.pid AS blocked_pid, blocking_locks.pid AS blocking_pid FROM pg_catalog.pg_locks blocked_locks JOIN pg_catalog.pg_locks blocking_locks ON blocking_locks.locktype = blocked_locks.locktype AND blocking_locks.DATABASE IS NOT DISTINCT FROM blocked_locks.DATABASE AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid AND blocking_locks.pid != blocked_locks.pid;
7. 常见问题排查
问题1:迁移后查询报错"column does not exist"
- 检查:字段名大小写是否一致
- 解决方案:统一使用小写字段名,或严格使用双引号包裹
问题2:二进制数据读取报错
- 检查:MyBatis是否误加了jdbcType属性
- 验证:直接使用JDBC读取测试
问题3:性能下降严重
- 检查:执行计划是否走错索引
- 工具:使用
EXPLAIN ANALYZE分析查询
问题4:事务隔离问题
- 现象:出现不可重复读
- 解决:显式设置事务隔离级别为REPEATABLE READ
8. 迁移后的验证策略
数据一致性检查:
# 使用pandas进行抽样比对 import pandas as pd from sqlalchemy import create_engine mysql_engine = create_engine('mysql://user:pass@mysql-host:3306/db') gauss_engine = create_engine('postgresql://user:pass@gauss-host:5432/db') df_mysql = pd.read_sql("SELECT * FROM users SAMPLE 1000", mysql_engine) df_gauss = pd.read_sql("SELECT * FROM users TABLESAMPLE SYSTEM(1)", gauss_engine) pd.testing.assert_frame_equal(df_mysql, df_gauss)性能基准测试:
- 使用JMeter模拟生产流量
- 对比TPS、响应时间等关键指标
应用兼容性测试:
- 全量回归测试
- 重点测试包含二进制操作的场景
9. 进阶技巧:自动化迁移工具
对于大型系统,建议使用迁移工具链:
Schema转换:
# 使用pgloader工具 pgloader mysql://user:pass@host:3306/db \ postgresql://user:pass@host:5432/db数据校验:
# 使用data-diff工具>