【金仓数据库征文】从MySQL到金仓:异构数据库迁移全流程实战与性能优化
前言
数据库迁移是一项复杂的系统工程,涉及数据结构迁移、应用改造、性能优化等多个环节。从MySQL迁移到金仓数据库,既要保证数据完整性,又要确保应用平滑过渡,还要实现性能的全面提升。
本篇内容完整记录了一次从MySQL到金仓的真实迁移项目,详细讲解迁移方案设计、对象迁移、应用适配、性能优化以及迁移验证等关键环节。全文以实际操作为主,结合大量真实案例。如果你正在规划数据库迁移项目,或者需要参考迁移经验,相信这篇内容对你会有帮助。
一、迁移方案设计与评估
迁移前的充分准备是项目成功的关键。首先评估迁移范围和对象复杂度,然后选择合适的迁移工具,最后生成兼容性评估报告。
sql
-- 评估MySQL数据库规模
SELECT
table_schema,
COUNT(*) AS table_count,
SUM(table_rows) AS total_rows,
ROUND(SUM(data_length + index_length) / 1024 / 1024 / 1024, 2) AS total_size_gb
FROM information_schema.tables
WHERE table_schema NOT IN ('mysql', 'information_schema', 'performance_schema', 'sys')
GROUP BY table_schema;
-- 评估对象复杂度
SELECT
routine_schema,
routine_type,
COUNT(*) AS count
FROM information_schema.routines
WHERE routine_schema NOT IN ('mysql', 'information_schema')
GROUP BY routine_schema, routine_type
ORDER BY routine_schema, routine_type;
接下来配置迁移工具:
bash
# 金仓迁移工具(KDTS)使用
# 1. 创建迁移项目
kdts create --source mysql --target kingbase --project mysql_migration
# 2. 配置源端连接
kdts config source \
--host 192.168.1.100 \
--port 3306 \
--user root \
--password xxx \
--database source_db
# 3. 配置目标端连接
kdts config target \
--host 192.168.1.200 \
--port 54321 \
--user system \
--password xxx \
--database target_db
最后生成兼容性评估报告,检查不兼容的数据类型和函数:
sql
-- 检查不兼容的数据类型
SELECT
table_name,
column_name,
data_type,
column_type
FROM information_schema.columns
WHERE table_schema = 'source_db'
AND (
data_type IN ('enum', 'set')
OR column_type LIKE '%unsigned%'
OR column_type LIKE '%zerofill%'
);
-- 检查不兼容的函数
SELECT
routine_name,
routine_definition
FROM information_schema.routines
WHERE routine_schema = 'source_db'
AND (
routine_definition LIKE '%IFNULL%'
OR routine_definition LIKE '%NOW()%'
OR routine_definition LIKE '%GROUP_CONCAT%'
);
二、表结构与数据迁移
表结构迁移是迁移的基础。首先处理数据类型映射和约束转换,然后进行批量数据迁移,最后验证数据一致性。
数据类型映射:将MySQL的表结构转换为金仓兼容的格式。
sql
-- MySQL原始表结构
CREATE TABLE users (
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age TINYINT UNSIGNED,
balance DECIMAL(10,2) UNSIGNED,
status ENUM('active', 'inactive', 'deleted'),
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
extra JSON
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 金仓转换后表结构
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
username VARCHAR(50) NOT NULL,
email VARCHAR(100),
age SMALLINT CHECK (age >= 0 AND age <= 255),
balance NUMERIC(10,2) CHECK (balance >= 0),
status VARCHAR(20) CHECK (status IN ('active', 'inactive', 'deleted')),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
extra JSONB
);
-- 创建updated_at自动更新触发器
CREATE OR REPLACE FUNCTION update_timestamp()
RETURNS TRIGGER AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER trg_users_update
BEFORE UPDATE ON users
FOR EACH ROW EXECUTE FUNCTION update_timestamp();
批量数据迁移:使用脚本自动化迁移数据。
bash
#!/bin/bash
# migrate_data.sh - 批量数据迁移脚本
SOURCE_HOST="192.168.1.100"
SOURCE_PORT="3306"
SOURCE_USER="root"
SOURCE_PASS="xxx"
SOURCE_DB="source_db"
TARGET_HOST="192.168.1.200"
TARGET_PORT="54321"
TARGET_USER="system"
TARGET_PASS="xxx"
TARGET_DB="target_db"
# 导出MySQL数据
mysqldump -h $SOURCE_HOST -P $SOURCE_PORT -u $SOURCE_USER -p$SOURCE_PASS \
--single-transaction --quick --lock-tables=false \
$SOURCE_DB users > /tmp/users.sql
# 转换SQL语法
sed -i 's/AUTO_INCREMENT/SERIAL/g' /tmp/users.sql
sed -i 's/ENGINE=InnoDB//g' /tmp/users.sql
sed -i 's/DEFAULT CHARSET=utf8mb4//g' /tmp/users.sql
# 导入金仓
sys_import -h $TARGET_HOST -p $TARGET_PORT -u $TARGET_USER -p$TARGET_PASS \
-d $TARGET_DB /tmp/users.sql
echo "数据迁移完成:$(date)"
数据一致性验证:通过统计和MD5校验确保数据完整。
sql
-- 源端统计
SELECT
COUNT(*) AS total_count,
SUM(balance) AS total_balance,
MIN(created_at) AS min_date,
MAX(created_at) AS max_date
FROM users;
-- 目标端统计(应该与源端一致)
SELECT
COUNT(*) AS total_count,
SUM(balance) AS total_balance,
MIN(created_at) AS min_date,
MAX(created_at) AS max_date
FROM users;
-- MD5校验(抽样验证)
SELECT MD5(GROUP_CONCAT(id, username, balance ORDER BY id)) AS checksum
FROM users LIMIT 1000;
三、存储过程与函数迁移
存储过程迁移需要处理语法差异和函数兼容性。以MySQL存储过程为例,转换为金仓的PL/pgSQL函数。
MySQL存储过程:
sql
-- MySQL原始存储过程
DELIMITER $$
CREATE PROCEDURE update_user_balance(
IN p_user_id BIGINT,
IN p_amount DECIMAL(10,2),
OUT p_result INT
)
BEGIN
DECLARE v_balance DECIMAL(10,2);
SELECT balance INTO v_balance
FROM users
WHERE id = p_user_id;
IF v_balance >= p_amount THEN
UPDATE users
SET balance = balance - p_amount
WHERE id = p_user_id;
SET p_result = 0;
ELSE
SET p_result = -1;
END IF;
END$$
DELIMITER ;
金仓转换后存储过程:
sql
-- 金仓转换后存储过程
CREATE OR REPLACE FUNCTION update_user_balance(
p_user_id BIGINT,
p_amount NUMERIC,
OUT p_result INT
)
RETURNS INT AS $$
DECLARE
v_balance NUMERIC;
BEGIN
SELECT balance INTO v_balance
FROM users
WHERE id = p_user_id;
IF v_balance >= p_amount THEN
UPDATE users
SET balance = balance - p_amount
WHERE id = p_user_id;
p_result := 0;
ELSE
p_result := -1;
END IF;
RETURN p_result;
END;
$$ LANGUAGE plpgsql;
-- 调用方式变更
-- MySQL: CALL update_user_balance(1, 100, @result);
-- 金仓: SELECT * FROM update_user_balance(1, 100);
函数兼容性处理:将MySQL特有函数转换为金仓对应函数。
sql
-- MySQL函数
-- IFNULL(value, default)
-- NOW()
-- GROUP_CONCAT(expr)
-- DATE_FORMAT(date, format)
-- 金仓对应函数
-- COALESCE(value, default)
-- CURRENT_TIMESTAMP 或 NOW()
-- STRING_AGG(expr, delimiter)
-- TO_CHAR(date, format)
-- 示例:转换查询语句
-- MySQL
SELECT
IFNULL(username, 'Unknown') AS username,
DATE_FORMAT(created_at, '%Y-%m-%d') AS create_date,
GROUP_CONCAT(role SEPARATOR ',') AS roles
FROM users
GROUP BY username;
-- 金仓
SELECT
COALESCE(username, 'Unknown') AS username,
TO_CHAR(created_at, 'YYYY-MM-DD') AS create_date,
STRING_AGG(role, ',') AS roles
FROM users
GROUP BY username;
四、应用适配与驱动替换
应用层需要适配金仓驱动和SQL语法。
JDBC驱动替换:更新Maven依赖和数据源配置。
xml
<!-- Maven依赖:MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- Maven依赖:金仓 -->
<dependency>
<groupId>com.kingbase</groupId>
<artifactId>kingbase8</artifactId>
<version>8.6.0</version>
</dependency>
数据源配置:修改Spring Boot配置文件。
yaml
# application.yml - MySQL配置
spring:
datasource:
url: jdbc:mysql://192.168.1.100:3306/source_db?useUnicode=true&characterEncoding=utf-8
username: root
password: xxx
driver-class-name: com.mysql.cj.jdbc.Driver
# application.yml - 金仓配置
spring:
datasource:
url: jdbc:kingbase8://192.168.1.200:54321/target_db
username: system
password: xxx
driver-class-name: com.kingbase.Driver
MyBatis方言适配:调整分页和批量插入语法。
xml
<!-- MySQL分页 -->
<select id="selectUsers" resultType="User">
SELECT * FROM users
LIMIT #{offset}, #{limit}
</select>
<!-- 金仓分页(语法相同) -->
<select id="selectUsers" resultType="User">
SELECT * FROM users
LIMIT #{limit} OFFSET #{offset}
</select>
<!-- MySQL批量插入 -->
<insert id="batchInsert">
INSERT INTO users (username, email) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.username}, #{item.email})
</foreach>
</insert>
<!-- 金仓批量插入(语法相同) -->
<insert id="batchInsert">
INSERT INTO users (username, email) VALUES
<foreach collection="list" item="item" separator=",">
(#{item.username}, #{item.email})
</foreach>
</insert>
五、迁移后性能优化
迁移完成后,针对性能瓶颈进行优化。
索引优化:根据查询模式创建合适的索引。
sql
-- MySQL索引
CREATE INDEX idx_username ON users(username);
CREATE INDEX idx_created ON users(created_at);
-- 金仓索引优化
CREATE INDEX idx_users_username ON users(username);
CREATE INDEX idx_users_created ON users(created_at DESC);
CREATE INDEX idx_users_composite ON users(status, created_at DESC);
-- 查看索引使用情况
SELECT
indexname,
idx_scan,
idx_tup_read,
idx_tup_fetch
FROM sys_stat_user_indexes
WHERE tablename = 'users'
ORDER BY idx_scan DESC;
查询优化:改写低效SQL,利用执行计划分析。
sql
-- MySQL查询(性能较差)
SELECT * FROM users
WHERE DATE(created_at) = '2026-06-23';
-- 金仓优化查询
SELECT * FROM users
WHERE created_at >= '2026-06-23'
AND created_at < '2026-06-24';
-- 执行计划分析
EXPLAIN ANALYZE
SELECT * FROM users
WHERE created_at >= '2026-06-23'
AND created_at < '2026-06-24';
连接池优化:调整HikariCP配置参数。
yaml
# HikariCP配置优化
spring:
datasource:
hikari:
maximum-pool-size: 30 # 根据CPU核心数调整
minimum-idle: 10
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
# 金仓特定优化
connection-test-query: SELECT 1
validation-timeout: 5000
六、实战案例解析
场景一:大表迁移性能优化
某核心业务表数据量达到5000万行,迁移耗时过长。
问题:初始迁移方案使用mysqldump导出,导入金仓耗时超过8小时。
优化方案:
bash
#!/bin/bash
# parallel_migrate.sh - 并行迁移脚本
TABLES=("users" "orders" "products" "inventory")
PARALLEL_JOBS=4
migrate_table() {
local table=$1
echo "开始迁移表:$table"
# 导出
mysqldump -h $SOURCE_HOST -P $SOURCE_PORT -u $SOURCE_USER -p$SOURCE_PASS \
--single-transaction $SOURCE_DB $table > /tmp/${table}.sql
# 转换
sed -i 's/AUTO_INCREMENT/SERIAL/g' /tmp/${table}.sql
# 导入
sys_import -h $TARGET_HOST -p $TARGET_PORT -u $TARGET_USER -p$TARGET_PASS \
-d $TARGET_DB /tmp/${table}.sql
echo "表 $table 迁移完成"
}
# 并行迁移
for table in "${TABLES[@]}"; do
migrate_table $table &
done
wait
echo "所有表迁移完成"
优化效果:迁移时间从8小时缩短到2小时。
场景二:存储过程兼容性问题
某复杂存储过程包含大量MySQL特有语法,迁移失败。
问题:存储过程中使用了GROUP_CONCAT、临时表、游标等MySQL特性。
解决方案:
sql
-- MySQL原始代码
CREATE PROCEDURE process_orders()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE v_order_id BIGINT;
DECLARE cur CURSOR FOR
SELECT order_id FROM orders
WHERE status = 'pending'
GROUP BY order_id
HAVING GROUP_CONCAT(product_id) LIKE '%1001%';
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
CREATE TEMPORARY TABLE temp_results (order_id BIGINT);
OPEN cur;
read_loop: LOOP
FETCH cur INTO v_order_id;
IF done THEN LEAVE read_loop; END IF;
INSERT INTO temp_results VALUES (v_order_id);
END LOOP;
CLOSE cur;
SELECT * FROM temp_results;
DROP TEMPORARY TABLE temp_results;
END;
-- 金仓转换后
CREATE OR REPLACE FUNCTION process_orders()
RETURNS TABLE(order_id BIGINT) AS $$
DECLARE
v_order_id BIGINT;
v_products TEXT;
BEGIN
CREATE TEMP TABLE temp_results (order_id BIGINT) ON COMMIT DROP;
FOR v_order_id, v_products IN
SELECT order_id, STRING_AGG(product_id::TEXT, ',')
FROM orders
WHERE status = 'pending'
GROUP BY order_id
LOOP
IF v_products LIKE '%1001%' THEN
INSERT INTO temp_results VALUES (v_order_id);
END IF;
END LOOP;
RETURN QUERY SELECT * FROM temp_results;
END;
$$ LANGUAGE plpgsql;
场景三:迁移后性能下降
迁移后某些查询性能明显下降。
问题:某些复杂查询在金仓中执行时间比MySQL长3倍。
优化过程:
sql
-- 问题查询
SELECT
u.username,
COUNT(o.order_id) AS order_count,
SUM(o.amount) AS total_amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at >= '2025-01-01'
GROUP BY u.username
ORDER BY total_amount DESC
LIMIT 10;
-- 执行计划分析
EXPLAIN ANALYZE
SELECT
u.username,
COUNT(o.order_id) AS order_count,
SUM(o.amount) AS total_amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.created_at >= '2025-01-01'
GROUP BY u.username
ORDER BY total_amount DESC
LIMIT 10;
-- 优化方案:创建复合索引
CREATE INDEX idx_users_created ON users(created_at, id);
CREATE INDEX idx_orders_user ON orders(user_id, order_id, amount);
-- 优化后查询时间从15秒降至800毫秒
总结与展望
从MySQL到金仓的迁移是一项系统工程,需要充分的准备和细致的执行。
核心原则:
- 迁移前充分评估,制定详细的迁移方案
- 数据类型映射要准确,处理兼容性问题
- 存储过程迁移需要语法转换和功能验证
- 应用层适配驱动和SQL方言
- 迁移后进行性能优化,确保系统稳定
金仓数据库对MySQL的兼容性良好,大部分场景下可以实现平滑迁移。在实际项目中,建议分阶段执行,先迁移非核心业务验证方案,再迁移核心业务,确保迁移成功率。
期望本篇内容能够为数据库迁移项目提供参考,帮助你顺利完成从MySQL到金仓的迁移工作。