最近在搞一个历史数据迁移的任务,涉及到一堆 MySQL 表的数据备份和导入。为了不让手动操作搞得太累,我们写了个 Bash 脚本来搞定这事儿。今天就来分享一下这个脚本的思路和一些细节。
背景
我们有几个 MySQL 数据库,里面存了很多历史数据。这些数据是按月分区的,时间跨度还挺大。现在需要把这些数据迁移到另一个数据库里,顺便把原来的分区删掉,腾出点空间。
脚本思路
-
配置部分:脚本一开始就是一堆配置,比如源数据库和目标数据库的连接信息,还有要处理的表名、数据库名啥的。这些配置可以根据环境不同进行调整。
-
日期处理:我们用了
date命令来处理日期,计算备份的时间范围。比如,我们要从某个时间点开始,按月备份数据,直到当前时间的前几个月。 -
备份和导入:用
mysqldump来备份数据,然后通过mysql命令把数据导入到目标数据库。备份的时候,我们还按时间片来分片备份,避免一次性操作太多数据。 -
分区删除:备份完数据后,脚本还会自动删除原数据库中的对应分区。这部分是通过查询
INFORMATION_SCHEMA.PARTITIONS来找到要删除的分区,然后执行ALTER TABLE ... DROP PARTITION来删除。 -
日志记录:整个过程都有详细的日志记录,方便排查问题。每次备份的月份也会记录在一个日志文件里,下次运行脚本时会接着上次的进度继续。
遇到的问题
-
时间处理:日期计算这块儿有点绕,特别是涉及到跨年、跨月的时候。我们用了
date -d来处理这些日期计算,但还是得小心边界情况。 -
分区删除:删除分区的时候,得确保备份的数据已经成功导入到目标数据库,不然就丢数据了。所以脚本里加了很多检查,确保每一步都成功了才继续。
-
性能问题:一开始我们没分片备份,结果一次性操作太多数据,数据库扛不住。后来改成按时间片分片备份,问题就解决了。
脚本实例
#!/bin/bash
#生产 MySQL 配置
cd /home/appuser/scripts/migrate_historical_data/
DB_HOST="192.168.30.2"
DB_PORT="13306"
DB_USER="root"
DB_PASS="zfh250126"
DB_NAME_ARRAY=("wf_cmbp" "wf_cmbp" "wf_cmbp" "wf_cmbp" "wf_cmbp" "wf_cmbp" "wf_cmbp" "wf_cmbp" "wf_cmbp" "wf_cmbp" "sg-jfgl")
TABLE_NAME_ARRAY=("stat_point_energy" "data_power_cumulant" "data_cumulant" "stat_point_used" "data_power_instant" "data_power_harmonic" "stat_max_min_iu" "stat_max_min_p" "data_water_extend" "data_pmo_device_json" "data_pile_monitor")
TABLE_INTERVAL_ARRAY=(4 6 240 240 744 744 240 240 744 744 240)
TARGET_DB_HOST="192.168.30.11"
TARGET_DB_PORT="33306"
TARGET_DB_USER="root"
TARGET_DB_PASS="j^To!S2h9B#KhhZL"
# 设置时间间隔(秒)
#interval=$((6 * 60 * 60)) # 小时
#测试 MySQL 配置
#cd /home/tmp
#DB_HOST="172.18.100.7"
#DB_PORT="3306"
#DB_USER="root"
#DB_PASS="j^To!S2h9B#KhhZL"
#DB_NAME_ARRAY=("wf_cmbp" "wf_cmbp" "sg-jfgl")
#TABLE_NAME_ARRAY=("data_power_cumulant" "data_cumulant" "data_pile_monitor")
# 备份到此数据库
#TARGET_DB_HOST="172.18.108.6"
#TARGET_DB_PORT="3307"
#TARGET_DB_USER="root"
#TARGET_DB_PASS="b3B0ls1ooLu5*1HV"
#TARGET_DB_PASS="sunrise"
#interval=$((372 * 60 * 60)) # 小时
# 日期格式化函数
format_date() {
date -d "@$1" "+%Y-%m-%d %H:%M:%S"
}
# 备份的最大月份数
max_backup_months=1
# 定义文件路径
backup_dir="./backup"
log_dir="./logs"
# 创建备份目录
mkdir -p "$backup_dir"
mkdir -p "$log_dir"
console_file="${log_dir}/exportAndCopy_$(date +%Y%m%d_%H%M%S).log"
# 获取当前时间的3年前的上个月
current_last_month=$(date -d "$(date +%Y-%m-15) -37 month" +%Y-%m)
echo "迁移数据的最后月是: $current_last_month" >> "$console_file"
len=${#TABLE_NAME_ARRAY[@]}
for (( i=0; i<$len; i++ )); do
DB_NAME=${DB_NAME_ARRAY[$i]}
TABLE_NAME=${TABLE_NAME_ARRAY[$i]}
interval=$((${TABLE_INTERVAL_ARRAY[$i]} * 60 * 60))
echo "db: $DB_NAME table: $TABLE_NAME" >> "$console_file"
log_file="${log_dir}/${TABLE_NAME}_log.txt"
# 获取最后备份的月份
if test -f "$log_file"; then
last_backup_month=$(tail -n 1 "$log_file")
else
last_backup_month="2018-11"
fi
echo "$(date +%Y%m%d_%H%M%S): 最后的备份月份是: $last_backup_month" >> "$console_file"
# 获取下一个备份月份
next_backup_month=$(date -d "$last_backup_month-15 +1 month" +%Y-%m)
# 开始备份循环
for ((j=1; j<=$max_backup_months; j++)); do
# 如果下一个备份月份超过当前的上个月,停止备份
if [[ "$next_backup_month" > "$current_last_month" ]]; then
echo "$(date +%Y%m%d_%H%M%S): 已经达到最大备份月份 $current_last_month, 停止备份。" >> "$console_file"
break
fi
# 备份 MySQL 指定表的数据到文件
echo "$(date +%Y%m%d_%H%M%S): 正在备份${TABLE_NAME} ${next_backup_month} ... " >> "$console_file"
# 计算当前备份月份的开始和结束日期
start_date="${next_backup_month}-01"
end_date=$(date -d "$start_date +1 month" +%Y-%m-01)
echo "$(date +%Y%m%d_%H%M%S): start: $start_date -> end: $end_date" >> "$console_file"
#按月备份,分片备份
current=$(date -d "$start_date" +%s)
end=$(date -d "$end_date" +%s)
count=1
while [ $current -lt $end ]; do
next=$((current + interval))
if [ $next -gt $end ]; then
next=$end
fi
start_datetime=$(format_date $current)
end_datetime=$(format_date $next)
echo "$(date +%Y%m%d_%H%M%S): start: $start_datetime -> end: $end_datetime" >> "$console_file"
# 使用 mysqldump 备份,指定过滤条件
backup_file_path="${backup_dir}/${TABLE_NAME}_${next_backup_month}_$(date +%H%M%S)_${count}.sql"
# 条件判断:j = 1 没有时间段, j > 1 按时间段导出数据
if [[ $j -eq 1 && $count -eq 1 ]]; then
echo "backup data's condition: 2019-01-01 <= data_time < $end_datetime" >> "$console_file"
mysqldump -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" "$TABLE_NAME" \
--no-create-info \
--where="data_time >= '2019-01-01' and data_time < '$end_datetime'" \
> "${backup_file_path}"
else
echo "backup data's condition: $start_datetime <= data_time < $end_datetime" >> "$console_file"
mysqldump -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" "$TABLE_NAME" \
--no-create-info \
--where="data_time >= '$start_datetime' AND data_time < '$end_datetime'" \
> "${backup_file_path}"
fi
# 使用 sed 删除 SET @@GLOBAL.GTID_PURGED= 开头直到分号结束的部分
sed -i '/^SET @@GLOBAL.GTID_PURGED=/,/;/d' ${backup_file_path}
# 检查备份是否成功
if [ $? -eq 0 ]; then
echo "$(date +%Y%m%d_%H%M%S): $DB_NAME $TABLE_NAME $next_backup_month_$count 的数据备份成功" >> "$console_file"
else
echo "$(date +%Y%m%d_%H%M%S): $DB_NAME $TABLE_NAME $next_backup_month_$count 的数据备份失败" >> "$console_file"
exit 1
fi
#导入数据
mysql -h"$TARGET_DB_HOST" -P"$TARGET_DB_PORT" -u"$TARGET_DB_USER" -p"$TARGET_DB_PASS" "$DB_NAME" < "${backup_file_path}"
# 检查备份是否成功
if [ $? -eq 0 ]; then
echo "$(date +%Y%m%d_%H%M%S): $DB_NAME $TABLE_NAME $next_backup_month_$count 的数据导入成功" >> "$console_file"
else
echo "$(date +%Y%m%d_%H%M%S): $DB_NAME $TABLE_NAME $next_backup_month_$count 的数据导入失败" >> "$console_file"
exit 1
fi
current=$next
((count++))
done
echo "正在查找需要删除的分区,日期范围: $start_date 到 $end_date" >> "$console_file"
# 查询要删除的分区名称
partition_name=$(mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" -D"$DB_NAME" -sse "
SELECT PARTITION_NAME
FROM INFORMATION_SCHEMA.PARTITIONS
WHERE TABLE_SCHEMA = '$DB_NAME'
AND TABLE_NAME = '$TABLE_NAME'
AND PARTITION_DESCRIPTION like '%$end_date%'
limit 1;
")
# 检查是否找到分区
if [ -z "$partition_name" ]; then
echo "$(date +%Y%m%d_%H%M%S): 未找到符合日期范围的分区" >> "$console_file"
else
echo "$(date +%Y%m%d_%H%M%S): 正在删除分区: $DB_NAME $TABLE_NAME $partition_name" >> "$console_file"
# 删除分区
delete_partition_sql="ALTER TABLE $TABLE_NAME DROP PARTITION $partition_name;"
mysql -h"$DB_HOST" -P"$DB_PORT" -u"$DB_USER" -p"$DB_PASS" -e "$delete_partition_sql" "$DB_NAME"
# 检查删除是否成功
if [ $? -eq 0 ]; then
echo "$(date +%Y%m%d_%H%M%S): $DB_NAME $TABLE_NAME $partition_name 分区删除成功" >> "$console_file"
else
echo "$(date +%Y%m%d_%H%M%S): $DB_NAME $TABLE_NAME $partition_name 分区删除失败" >> "$console_file"
exit 1
fi
fi
# 将最新备份月份写入 backup.txt
echo "$next_backup_month" >> "$log_file"
# 更新下一个备份月份
next_backup_month=$(date -d "$next_backup_month-15 +1 month" +%Y-%m)
done
done
echo "$(date +%Y%m%d_%H%M%S): 所有备份操作完成。" >> "$console_file"
总结
这个脚本虽然看起来有点复杂,但其实思路挺清晰的。通过自动化脚本,我们省去了很多手动操作的麻烦,也减少了出错的可能性。如果你也有类似的数据迁移需求,可以参考一下这个脚本的思路,说不定能帮上忙。
当然,脚本还有很多可以优化的地方,比如加入更多的错误处理,或者支持更多的数据库类型。不过目前来说,它已经很好地完成了我们的任务。如果你有更好的想法,欢迎一起讨论!
199

被折叠的 条评论
为什么被折叠?



