重建mysql从库、表数据迁移
一、背景
1、xtrabackup+ systemd管理
cat /etc/systemd/system/mysql_slave.service [Unit] Description=MySQL Slave Container After=docker.service Requires=docker.service [Service] TimeoutStartSec=60 RuntimeDirectory=mysqld RuntimeDirectoryMode=0777 ExecStartPre=-/usr/bin/docker rm -f mysql_slave ExecStartPre=-/usr/bin/docker pull "ip:5000/mysql:5.7" ExecStartPre=/usr/bin/docker volume create mysqls_data ExecStartPre=/usr/bin/docker volume create mysqls_log ExecStart=/backup/mysql/mysql_slave.run ExecStop=-/usr/bin/docker stop mysql_slave Restart=on-failure RestartSec=10 [Install] WantedBy=multi-user.target
启动文件
cat /backup/mysql/mysql_slave.run #!/bin/bash /usr/bin/docker run -i \ -e MYSQL_ROOT_PASSWORD=xxx \ -e MYSQL_DATABASE=xxx \ -e LANG=C.UTF-8 \ -v /backup/mysql/mysql_slave_my.cnf:/etc/mysql/my.cnf \ -v /var/run/mysqld:/var/run/mysqld \ -v mysqls_data:/var/lib/mysql \ -v mysqls_log:/var/log/mysql \ --privileged=true \ --net=host \ --rm \ --name=mysql_slave \ "ip:5000/mysql:5.7"
2、mysql的配置文件
bin log 部分,设置bin log保存时间、格式等
[mysql] default-character-set = utf8 [mysqld] # Server options tmpdir = /tmp transaction_isolation = READ-COMMITTED sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION" symbolic-links = 0 # Server System Variables character_set_server = utf8 max_allowed_packet = 32M max_connections = 2000 max_connect_errors = 10000 key_buffer_size = 32M sort_buffer_size = 16M join_buffer_size = 4M read_buffer_size = 32M read_rnd_buffer_size = 32M bulk_insert_buffer_size = 16M query_cache_type = 0 slow_query_log = 1 slow_query_log_file = /var/log/mysql/mysql-slow.log long_query_time = 3 lower_case_table_names = 1 wait_timeout = 86400 interactive_timeout = 43200 explicit_defaults_for_timestamp = 1 secure_file_priv = NULL skip_name_resolve = ON # Binary Logging Options and Variables server-id = 1 binlog-format = ROW log-bin = mysql-bin log_error = /var/log/mysql/mysql.err binlog-ignore-db = mysql binlog_cache_size = 16M expire_logs_days = 99 innodb_buffer_pool_size = 8589934592
3、bin log三种格式
MySQL 的二进制日志(binlog)是用于记录更改数据库状态的日志文件,主要用于数据恢复或复制。
MySQL 支持三种不同的 binlog 格式:Statement、Row 和 Mixed。
1. Statement-Based Logging (声明式日志)
描述
- 在这种模式下,MySQL 记录的是执行的 SQL 语句,而不是实际数据更改的行。
- 每当执行一个更改数据的 SQL 语句(如
INSERT
、UPDATE
或DELETE
)时,相关的 SQL 语句会以文本形式记录到 binlog 中。
特点
-
优点:
- 存储空间占用较少,因为只记录 SQL 语句,而非数据的具体变化。
- 任何能够被 SQL 引擎执行的更改都可以被记录,保持了灵活性。
-
缺点:
- 在某些情况下,由于当前上下文的差异(如函数的非确定性行为、时间戳等),相同的 SQL 语句在主从服务器上可能导致不一致。
- 可能会导致某些语句在复制时无法完全重放,由于不同时刻的承诺或环境差异,导致更新数据时可能出现问题。
2. Row-Based Logging (行式日志)
描述
- 在行式日志中,MySQL 记录的是实际更改的表的行数据,而非整个 SQL 语句。
- 当对表进行 INSERT、UPDATE 或 DELETE 操作时,具体更改的行信息(包括哪些行发生了什么更改)会被记录。
特点
-
优点:
- 数据的准确性高,因为每个行的更改都有明确记录。
- 复制时更可靠,不会受到 SQL 语句上下文的影响。
-
缺点:
- 相对于声明式日志,行式日志占用的存储空间较大,因为每个更改都记录了具体的行信息。
- 如果涉及到大批量的数据变更,日志的体积增长可能会很快。
3. Mixed Logging (混合日志)
描述
- 混合日志模式结合了声明式和行式日志的优点。根据具体 SQL 语句的特性,MySQL 可以选择使用声明式或行式日志。
- 对于简单的语句,MySQL 可能选择声明式日志,而对于那些可能出现复制不一致的复杂语句,选择行式日志。
特点
-
优点:
- 在性能与数据一致性之间提供了更好的平衡。
- 充分利用声明式日志的存储效率,同时在必要时确保数据完整性和准确性。
-
缺点:
- 在一些情况下,可能会导致理解和管理上的复杂性,因为不同语句使用不同的日志记录方法。
- 依赖于 MySQL 的内部逻辑决定使用哪种日志模式。
总结
选择使用哪种 binlog 格式要考虑应用程序的需求、数据一致性要求以及性能考虑。通常来说,在主从复制环境中,行式日志因其数据的准确性和一致性,提供了更强的保障,
而声明式日志由于其较小的存储需求和较好的性能,适合不要求极高一致性的场景。混合模式则是一个折衷方案,能灵活调整。
二、实操
在mysql slave节点操作
1、停止mysql服务
systemctl stop mysql_slave.service
2、重建mysql从库的数据卷
docker volume rm mysqls_data docker volume rm mysqls_log docker volume create mysqls_data docker volume create mysqls_log
注意:清理目录后,不要再启动mysql从库服务
3、临时开放防火墙端口
firewall-cmd --zone=public --add-port=20000/tcp
4、开启接收服务
nc -l 20000|xbstream -x -C /healsci/docker/volumes/mysqls_data/_data/
5、主库操作
xtrabackup --defaults-file=/backup/mysql/mysql_master_my.cnf --backup --user=root --password=xxx --socket=/var/run/mysqld/mysqld.sock --datadir=/healsci/docker/volumes/mysqlm_data/_data/ --parallel=8 --compress --compress-threads=8 --stream=xbstream | nc node3 20000
备份期间,不能执行ddl操作,否则备份会失败。
当执行结束后,最后一行显示有 completed OK!,代表备份操作顺利完成。
6、在mysql从库节点执行以下命令:
firewall-cmd --zone=public --remove-port=20000/tcp xtrabackup --decompress --remove-original --parallel=8 --target-dir=/heal/docker/volumes/mysqls_data/_data xtrabackup --prepare --use-memory=4G --target-dir=/heal/docker/volumes/mysqls_data/_data
7、启动mysql从库
systemctl start mysql_slave.service mysql -uroot -pxxx -S /var/run/mysqld/mysqld.sock CHANGE MASTER TO MASTER_HOST='ip',MASTER_USER='repl',MASTER_PASSWORD='repl',MASTER_LOG_FILE='mysql-bin.xxx',MASTER_LOG_POS=xxx;
MASTER_LOG_FILE和MASTER_LOG_POS值获取:
当用主库备份时,查询备份目录(/heal/docker/volumes/mysqls_data/_data/xtrabackup_info)文件中的binlog_pos行,对应filename和position
8、启动mysql主从同步
start slave; show slave status\G;
三、使用脚本重建从库
1、依赖
使用xtrabackup程序采用流的方式备份主库
openeuler-22.03-LTS:
percona-xtrabackup-24,percona-toolkit,qpress,nmap,tmux,jq,yq
centos-7:
percona-xtrabackup-24,percona-toolkit,qpress,nmap,tmux.nmap-ncat
注:
qpress
是一个高效的文件压缩工具,特别适合用于快速压缩和解压缩文件。qpress
经常与数据库备份工具(如 Percona XtraBackup)一起使用,用于高效地压缩备份文件
percona-toolkit 是一个工具集,大致包含数据一致性检查、查询性能诊断、数据库复制管理、数据归档、索引优化
ls /usr/bin/pt-* /usr/bin/pt-align /usr/bin/pt-fingerprint /usr/bin/pt-mongodb-index-check /usr/bin/pt-secure-collect /usr/bin/pt-table-checksum /usr/bin/pt-archiver /usr/bin/pt-fk-error-logger /usr/bin/pt-mongodb-query-digest /usr/bin/pt-show-grants /usr/bin/pt-table-sync /usr/bin/pt-config-diff /usr/bin/pt-heartbeat /usr/bin/pt-mongodb-summary /usr/bin/pt-sift /usr/bin/pt-table-usage /usr/bin/pt-deadlock-logger /usr/bin/pt-index-usage /usr/bin/pt-mysql-summary /usr/bin/pt-slave-delay /usr/bin/pt-upgrade /usr/bin/pt-diskstats /usr/bin/pt-ioprofile /usr/bin/pt-online-schema-change /usr/bin/pt-slave-find /usr/bin/pt-variable-advisor /usr/bin/pt-duplicate-key-checker /usr/bin/pt-k8s-debug-collector /usr/bin/pt-pg-summary /usr/bin/pt-slave-restart /usr/bin/pt-visual-explain /usr/bin/pt-fifo-split /usr/bin/pt-kill /usr/bin/pt-pmp /usr/bin/pt-stalk /usr/bin/pt-find /usr/bin/pt-mext /usr/bin/pt-query-digest /usr/bin/pt-summary
2、脚本
#!/bin/bash #Manually defined mysql node mysql_master_node=node1 if [[ ! -z ${mysql_master_node} ]];then mysql_master_volume=/healsci/docker/volumes/mysqlm_data/_data fi mysql_slave_node=node2 if [[ ! -z ${mysql_slave_node} ]];then mysql_slave_volume=/healsci/docker/volumes/mysqls_data/_data fi #add this option ,ssh connect to remote server,will not #ask you if need add new server fingerprint or change one #this oprion stay add or modify fingerprint at known_hosts readonly ssh_option="-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o LogLevel=QUIET" #Get os cpu numbers and xtrabackup use half readonly cpu_num=$(cat /proc/cpuinfo|grep processor|wc -l) readonly half_cpu_num=`expr ${cpu_num} / 2` readonly firewalld_port=20000 readonly status_pid="/tmp/mysql_rebuild.pid" readonly backup_log="/var/log/mysql_backup.log" readonly slave_info="/tmp/slave_info" readonly mysql_master_file="/backup/mysql/mysql_master_my.cnf" readonly mysql_slave_file="/backup/mysql/mysql_slave_my.cnf" readonly mysql_unix_sock="/var/run/mysqld/mysqld.sock" readonly mysql_master_service="mysql_master.service" readonly mysql_slave_service="mysql_slave.service" readonly xtrabackup_backup_option="--defaults-file=${mysql_master_file} --no-version-check --backup --user=root --password=xxx --socket=${mysql_unix_sock} --parallel=${half_cpu_num} --compress --compress-threads=${half_cpu_num} --stream=xbstream --target-dir=/tmp" readonly xtrabackup_decompress_option="--decompress --remove-original --parallel=${half_cpu_num}" readonly xtrabackup_prepare_option="--prepare --use-memory=4G" readonly pt_slave_find_option="--resolve-address --report-format=hostname" readonly mysql_dsn="u=root,p=xxx,S=/var/run/mysqld/mysqld.sock" function find_mysql_slave_node() { mysql_slave_node=$(ssh root@${mysql_master_node} "pt-slave-find ${pt_slave_find_option} ${mysql_dsn} 2>/dev/null|grep -|cut -d' ' -f3|cut -d'(' -f 2|cut -d')' -f 1") if [[ -z "${mysql_slave_node}" ]];then echo "Can't found any mysql slave node.Exit" exit 0 fi } function find_mysql_data_volume() { if [[ $1 == "master" ]];then mysql_container_name=$(ssh root@${mysql_master_node} 'docker ps --format "table {{.Names}}"|grep mysql_master') mysql_master_volume=$(ssh root@${mysql_master_node} docker inspect ${mysql_container_name}|jq -r '.[]|.Mounts|.[]|select(.Name=="mysqlm_data")|.Source') if [[ -z "${mysql_master_volume}" ]];then echo "Can't find mysql master data volume.Exit" exit 0 fi elif [[ $1 == "slave" ]];then mysql_container_name=$(ssh root@${mysql_slave_node} 'docker ps --format "table {{.Names}}"|grep mysql') mysql_slave_volume=$(ssh root@${mysql_slave_node} docker inspect ${mysql_container_name}|jq -r '.[]|.Mounts|.[]|select(.Name=="mysqls_data")|.Source') if [[ -z "${mysql_slave_volume}" ]];then echo "Can't find mysql slave data volume.Exit" exit 0 fi else echo "Can't find mysql any data volume.Exit" exit 0 fi } function manager_firewalld_port() { if [[ $1 == "add" ]];then ssh root@${mysql_slave_node} "firewall-cmd --zone=public --add-port=${firewalld_port}/tcp 1>/dev/null 2>&1" elif [[ $1 == "remove" ]];then ssh root@${mysql_slave_node} "firewall-cmd --zone=public --remove-port=${firewalld_port}/tcp 1>/dev/null 2>&1" else echo "Can't manager firewalld port for mysql backup.Exit" exit 0 fi } function create_session_tmux() { ssh ${ssh_option} root@$1 "tmux kill-server 1>/dev/null 2>&1" ssh ${ssh_option} root@$1 "tmux new -s $2 -d" ssh ${ssh_option} root@$1 "tmux send -t $2 '$3 && exit' Enter" } function test_node_port() { port_status=$(nmap -T4 -p ${firewalld_port} ${mysql_slave_node}|grep "${firewalld_port}/tcp"|grep open|wc -l) } function get_slave_info() { #Get slave info from file,because mysql slave service have been stop by backup mysql if [[ -f ${slave_info} ]];then local list=($(cat ${slave_info})) if [[ "x${#list[@]}" == "x2" ]];then mysql_slave_node=${list[0]} mysql_slave_volume=${list[1]} else echo "Can't find usefull info from ${slave_info}.Exit" exit 0 fi else if [[ -z ${mysql_slave_node} ]];then echo "Can't find mysql slave info.Exit" exit 0 fi fi } function reset_mysql_repl() { get_slave_info local tmux_en=$(ssh root@${mysql_slave_node} "tmux ls 2>/dev/null|grep mysql_prepare|wc -l") if [[ ${tmux_en} -eq 1 ]];then echo "Mysql prepare stay running.Exit" exit 0 elif [[ ${tmux_en} -eq 0 ]];then local check_log=$(ssh root@${mysql_slave_node} "tail -n 1 ${backup_log}|grep 'completed OK!'|wc -l") if [[ ${check_log} -eq 1 ]];then #Before start mysql check if set super_read_only is on ssh ${ssh_option} root@${mysql_slave_node} "sed -i 's/^\(super_read_only.*\)/#\1/g' ${mysql_slave_file}" skip_slave=$(ssh ${ssh_option} root@${mysql_slave_node} "egrep skip-slave-start ${mysql_slave_file}|wc -l") if [[ ${skip_slave} -eq 0 ]];then ssh ${ssh_option} root@${mysql_slave_node} "sed -i '$ a skip-slave-start' ${mysql_slave_file}" elif [[ ${skip_slave} -eq 1 ]];then ssh ${ssh_option} root@${mysql_slave_node} "sed -i 's/ #skip-slave-start$/skip-slave-start/g' ${mysql_slave_file}" else echo "Configure mysql slave fail.Exit." exit 0 fi ssh ${ssh_option} root@${mysql_slave_node} "systemctl start ${mysql_slave_service}" sleep 10 #Test if mysql slave start finally while : do ssh ${ssh_option} root@${mysql_slave_node} "test -S ${mysql_unix_sock}" if [[ $? -eq 0 ]];then break else sleep 5 fi done scp -Brq ${mysql_slave_node}:${mysql_slave_volume}/xtrabackup_info /tmp local mysql_bin_file=$(egrep "binlog_pos" /tmp/xtrabackup_info|awk -v FS="'" '{print $2}') local mysql_position=$(egrep "binlog_pos" /tmp/xtrabackup_info|awk -v FS="'" '{print $4}') cp -f ./mysql_slave_init.template /tmp/mysql_slave_init.sql sed -i -e "s/master_ip/${master_node_ip}/g" -e "s/mysql-bin.000001/${mysql_bin_file}/g" -e "s/MASTER_LOG_POS=1/MASTER_LOG_POS=${mysql_position}/g" /tmp/mysql_slave_init.sql scp -Brq /tmp/mysql_slave_init.sql ${mysql_slave_node}:/tmp #Setup mysql slave replication and start it ssh ${ssh_option} root@${mysql_slave_node} "mysql < /tmp/mysql_slave_init.sql" ssh ${ssh_option} root@${mysql_slave_node} "sed -i 's/ skip-slave-start$/#skip-slave-start/g' ${mysql_slave_file}" ssh ${ssh_option} root@${mysql_slave_node} "sed -i 's/ #\(super_read_only.*\)/\1/g' ${mysql_slave_file}" ssh ${ssh_option} root@${mysql_slave_node} "mysql -e 'set global super_read_only=on;'" ssh ${ssh_option} root@${mysql_master_node} "/usr/bin/rm -f ${slave_info} ${status_pid} '/tmp/mysql_slave_init.sql' '/tmp/xtrabackup_info'" ssh ${ssh_option} root@${mysql_slave_node} "/usr/bin/rm -f /tmp/mysql_slave_init.sql" echo "Script rebuil mysql on ${mysql_slave_node} is finally.Exit" exit 0 else echo "Mysql prepare status wrong.Exit" exit 0 fi else echo "Mysql prepare staying running.Exit" exit 0 fi } function prepare_mysql_slave() { echo -e "\033[31m Script will clean ${mysql_slave_volume} at ${mysql_slave_node}. \033[0m" echo -e "\033[31m This is the last chance to give up this operation. \033[0m" read -n1 -p "Are you sure to continue rebuild mysql slave?[Yy/Nn]:" answer case ${answer} in Y|y) #Prepare mysql slave #1:Stop mysql slave service ssh ${ssh_option} root@${mysql_slave_node} "systemctl stop ${mysql_slave_service} 1>/dev/null 2>&1" echo -e "\nWait stop ${mysql_slave_service} at ${mysql_slave_node}" while : do ssh ${ssh_option} root@${mysql_slave_node} "test -S ${mysql_unix_sock}" if [[ $? -eq 1 ]];then break else sleep 30 fi done #2:Recreate mysql slave volumes(include date and log volumes) local volume_list=($(ssh ${ssh_option} root@${mysql_slave_node} "docker volume ls --quiet|grep mysql")) for volume_name in ${volume_list[@]} do ssh ${ssh_option} root@${mysql_slave_node} "docker volume rm ${volume_name} 1>/dev/null 2>&1 && docker volume create ${volume_name} 1>/dev/null 2>&1" done ;; *) echo "Stop rebuild mysql slave.Exit" exit 0 ;; esac } function mysql_backup() { get_slave_info prepare_mysql_slave manager_firewalld_port add #Start nc to get mysql backup stream data local rev_command="nc -l ${firewalld_port}|xbstream -x -C ${mysql_slave_volume}" create_session_tmux ${mysql_slave_node} mysql_slave "${rev_command}" test_node_port if [[ ${port_status} -eq 1 ]];then #Start backup mysql to mysql slave node backup_command="xtrabackup ${xtrabackup_backup_option} --datadir=${mysql_master_volume} 2>${backup_log}|nc ${mysql_slave_node} ${firewalld_port}" create_session_tmux ${mysql_master_node} mysql_backup "${backup_command}" #Change pid file to 1 echo 1 > ${status_pid} echo "Start backup mysql to ${mysql_slave_node}.Please waiting later to run this script again to check if backup is success.Exit" exit 0 else echo "Port ${firewalld_port} on ${mysql_slave_node} status isn't open.Exit" exit 0 fi } function mysql_decompress() { get_slave_info local tmux_en=$(ssh ${ssh_option} root@${mysql_master_node} "tmux ls 2>/dev/null|grep mysql_backup|wc -l") #if tmux have session mysq_backup,this mean mysql backup stay running,exit script. if [[ ${tmux_en} -eq 1 ]];then echo "Mysql backup stay running.Exit" exit 0 elif [[ ${tmux_en} -eq 0 ]];then #Remove firewalld port manager_firewalld_port remove local check_log=$(tail -n 1 ${backup_log}|grep "completed OK!"|wc -l) if [[ ${check_log} -eq 1 ]];then decompress_command="xtrabackup ${xtrabackup_decompress_option} --target-dir=${mysql_slave_volume} 1>${backup_log} 2>&1" create_session_tmux ${mysql_slave_node} mysql_decompress "${decompress_command}" echo 2 > ${status_pid} echo "Start decompress mysql data at ${mysql_slave_node}.Please waiting later to run this script again to check if decompress is success.Exit" exit 0 else echo "Script check status is wrong.Exit" exit 0 fi else echo "Script check status is wrong.Exit" exit 0 fi } function mysql_prepare() { get_slave_info local tmux_en=$(ssh root@${mysql_slave_node} "tmux ls 2>/dev/null|grep mysql_decompress|wc -l") if [[ ${tmux_en} -eq 1 ]];then echo "Mysql decompress stay running.Exit" exit 0 elif [[ ${tmux_en} -eq 0 ]];then local check_log=$(ssh root@${mysql_slave_node} "tail -n 1 ${backup_log}|grep 'completed OK!'|wc -l") if [[ ${check_log} -eq 1 ]];then prepare_command="xtrabackup ${xtrabackup_prepare_option} --target-dir=${mysql_slave_volume} 1>${backup_log} 2>&1" create_session_tmux ${mysql_slave_node} mysql_prepare "${prepare_command}" echo 3 > ${status_pid} echo "Start prepare mysql data at ${mysql_slave_node}.Please waiting later to run this script again to check if prepare is success.Exit" exit 0 else echo "Script check status is wrong.Exit" exit 0 fi else echo "Script check status is wrong.Exit" exit 0 fi } #Main #Find out mysql master node if [[ -z ${mysql_master_node} ]];then node_list=($(egrep "node[0-5]" /backup/node-list|cut -d',' -f 1)) for node_name in ${node_list[@]} do mysql_master_en=$(ssh ${ssh_option} root@${node_name} "systemctl --type service|grep ${mysql_master_service}|grep active|grep running|wc -l") if [[ ${mysql_master_en} -eq 1 ]];then mysql_master_node=${node_name} break fi done fi master_node_ip=$(grep ${mysql_master_node} /backup/node-list|awk -v FS=',' '{print $2}') if [[ -z ${mysql_master_node} ]];then echo "Can't found any mysql master service on cluster.Exit" exit 0 fi if [[ -z "${mysql_master_volume}" ]];then find_mysql_data_volume master fi if [[ ! -f ${slave_info} ]];then if [[ -z ${mysql_slave_node} ]];then find_mysql_slave_node echo "${mysql_slave_node}" > ${slave_info} fi if [[ -z "${mysql_slave_volume}" ]];then find_mysql_data_volume slave echo "${mysql_slave_volume}" >> ${slave_info} fi fi #pid file,0 is no start,1 is mysql backup,2 is mysql decompress,3 is mysql prepare if [[ -f "${status_pid}" && -s "${status_pid}" ]];then run_status=$(cat ${status_pid}) if [[ ${run_status} != [0-3] ]];then echo 0 > ${status_pid} fi else echo 0 > ${status_pid} fi run_status=$(cat ${status_pid}) case ${run_status} in 0) mysql_backup ;; 1) mysql_decompress ;; 2) mysql_prepare ;; 3) reset_mysql_repl ;; *) ;; esac
3、脚本依赖文件
# cat mysql_slave_init.template
CHANGE MASTER TO MASTER_HOST="master_ip", MASTER_PORT=3306, MASTER_USER='repl',MASTER_PASSWORD='repl',MASTER_LOG_FILE='mysql-bin.000001',MASTER_LOG_POS=1; start slave;
注意:
从库执行sql文本时候,mysql < /tmp/mysql_slave_init.sql , 这里依赖于socket文件的映射
mysql> SHOW VARIABLES LIKE 'socket'; +---------------+-----------------------------+ | Variable_name | Value | +---------------+-----------------------------+ | socket | /var/run/mysqld/mysqld.sock | +---------------+-----------------------------+ 1 row in set (0.00 sec)
获取bin_log、log_positon
local mysql_bin_file=$(egrep "binlog_pos" /tmp/xtrabackup_info|awk -v FS="'" '{print $2}') local mysql_position=$(egrep "binlog_pos" /tmp/xtrabackup_info|awk -v FS="'" '{print $4}') sed -i -e s/master_ip/192.168.1.231/g -e s/mysql-bin.000001/mysql-bin.000014/g -e s/MASTER_LOG_POS=1/MASTER_LOG_POS=484569852/g /tmp/mysql_slave_init.sql
四、mysql 表数据迁移
1、MySQL中的.cfg、.ibd和.frm文件是与InnoDB存储引擎相关的文件。它们存储了有关数据库表的不同类型的信息:
.frm 文件
.frm文件是MySQL数据库中的表定义文件。对于每个表,MySQL都会在表所在的数据库目录下创建一个与表同名的.frm文件。此文件包含了表结构的元数据,包括列的定义(如数据类型、大小),索引信息,以及其他表属性。无论使用哪种存储引擎,都需要.frm文件来描述表的结构。从MySQL 5.7.31开始,.frm文件逐渐被废除,表结构元数据被保存在数据字典中。
.ibd 文件
.ibd
文件是 InnoDB 存储引擎的表空间文件,代表单个表的存储。在 MySQL 中,如果你使用了 InnoDB 的独立表空间模式(通过设置innodb_file_per_table=ON
),那么每个表的数据和索引都会存储在一个独自的.ibd
文件中。
主要特点
- 表结构与数据:每个
.ibd
文件包含表的结构信息和数据行的实际存储。这意味着表的所有行以及相关的索引信息都存储在这个文件中。 - 文件命名:文件名通常是表名加上
.ibd
后缀。例如,如果表名为my_table
,则文件名为my_table.ibd
。 - 独立性:每个
.ibd
文件是独立的,允许在管理数据库时更方便地备份、迁移或删除表。 - 元数据:
.ibd
文件还包含该表的元数据,如索引信息、数据行的格式等。
ibdata1
文件
ibdata1
是 InnoDB 的共享表空间文件(和其他共享文件,例如 undo 日志、元数据等)。在默认情况下(即没有启用innodb_file_per_table
),所有的 InnoDB 表的数据和索引都被存储在这个文件中。
主要特点
- 全局表空间:
ibdata1
是一个全局的表空间,所有 InnoDB 表的元数据、数据、索引等都存储在其中。该文件是共享的,包含了所有表的空间信息。 - 增量增长:随着数据的插入或更新,
ibdata1
文件会根据需要不断增长。它不会自动收缩。 - 回滚日志:除了存储表数据,
ibdata1
还存储 InnoDB 的撤消日志(undo log),这些用于处理事务回滚等操作。 - 表元数据:它存储了 InnoDB 表的结构信息和元数据,也包括 InnoDB 的内部控制信息。
总结
.ibd
文件用于存储每个 InnoDB 表的具体数据和索引,支持独立管理和操作,适用于需要单表管理的场景。ibdata1
文件是全局的共享表空间,包含所有 InnoDB 表的元数据和信息,同时承担了事务处理等功能,适用于不使用独立表空间的设置。
.cfg 文件
对于运行在MySQL 5.6和更高版本上的InnoDB表,.cfg文件用于保存表的压缩信息。当你使用mysqldump工具的—innodb-optimize-keys选项导出表结构为SQL时(或者其他表传输操作,比如Transportable Tablespaces功能),此文件会被创建。.cfg文件中存储了有关如何压缩表的信息,如行格式、键块大小等。这个文件不是用于常规操作,仅在表导出和导入之间传输压缩信息时才会用到。
请注意,从MySQL 8.0 版本开始,MySQL引入了Data Dictionary的概念替代.frm文件,数据字典是一个包含所有服务器对象元数据的事务性数据结构。在MySQL 8.0及以后的版本中,.frm文件和一些相关文件已经不再使用。
2、迁移脚本
#!/bin/bash src_db_host='node1' dest_db_host='node3' dest_file_host='node2' src_volume_name='mysqlm_data' dest_volume_name='mysqlm_data' dest_file_path_root='/dasda/database_backup/' mysql_user='root' mysql_password='xxxx' date=$(date +%Y%m%d) databases_tables1=( 'pbm_app.a_predict_flowing' 'pbm_app.patient_service_record' 'pbm_app.wait_beds_event' ) start_time=$(date +'%Y-%m-%d %H:%M:%S') start_timestamp=$(date +%s) # 导出表结构并创建表 for d_t in "${databases_tables1[@]}" do IFS="." read -ra database2table <<< "${d_t}" db="${database2table[0]}" tb="${database2table[1]}" # 建库 mysql -h"${dest_db_host}" -u"${mysql_user}" -p"${mysql_password}" -e "CREATE DATABASE IF NOT EXISTS \`${db}\`;" 2> /dev/null # 建表 sql=$(mysqldump --column-statistics=0 -u"${mysql_user}" -p"${mysql_password}" -h"${src_db_host}" "${db}" "${tb}" --no-data 2>&1 | tail -n +2) mysql -h "${dest_db_host}" -u"${mysql_user}" -p"${mysql_password}" -e "USE ${db}; ${sql}" 2> /dev/null # 目标实例上删除.ibd文件 mysql -h "${dest_db_host}" -u"${mysql_user}" -p"${mysql_password}" -e "USE "${db}"; ALTER TABLE "${tb}" DISCARD TABLESPACE;" 2> /dev/null # 原实例上导出.ibd 和 .cfg mysql -u"${mysql_user}" -h"${src_db_host}" -p"${mysql_password}" -e "FLUSH TABLES "${db}"."${tb}" FOR EXPORT; SELECT SLEEP(7200);" 2> /dev/null & export_pid=$! # 在后台开始同步.ibd 和 .cfg 文件到目标实例 ssh root@"${src_db_host}" "rsync -aP /healsci/docker/volumes/"${src_volume_name}"/_data/"${db}"/"${tb}".{ibd,cfg} root@"${dest_db_host}":/healsci/docker/volumes/"${dest_volume_name}"/_data/"${db}"/" & rsync_pid=$! # 确认文件同步完毕并验证stat值 while true; do local_stat_ibd=$(stat -c%s "/healsci/docker/volumes/${dest_volume_name}/_data/${db}/${tb}.ibd" 2> /dev/null) remote_stat_ibd=$(ssh root@"${src_db_host}" "stat -c%s /healsci/docker/volumes/${src_volume_name}/_data/${db}/${tb}.ibd" 2> /dev/null) local_stat_cfg=$(stat -c%s "/healsci/docker/volumes/${dest_volume_name}/_data/${db}/${tb}.cfg" 2> /dev/null) remote_stat_cfg=$(ssh root@"${src_db_host}" "stat -c%s /healsci/docker/volumes/${src_volume_name}/_data/${db}/${tb}.cfg" 2> /dev/null) if [[ -n "$local_stat_ibd" && -n "$remote_stat_ibd" && "$local_stat_ibd" == "$remote_stat_ibd" && -n "$local_stat_cfg" && -n "$remote_stat_cfg" && "$local_stat_cfg" == "$remote_stat_cfg" ]]; then break fi sleep 5 done # .cfg和.ibd文件一致,可结束MySQL会话 kill "${export_pid}" # 确保rsync进程已经完成 wait "${rsync_pid}" 2> /dev/null # 解锁原表 mysql -h"${src_db_host}" -u"${mysql_user}" -p"${mysql_password}" -e 'UNLOCK TABLES;' 2> /dev/null # 同步文件到备份节点 ssh root@"${dest_file_host}" "mkdir -p "${dest_file_path_root}""${date}"/"${db}"/" scp /healsci/docker/volumes/"${dest_volume_name}"/_data/"${db}"/"${tb}".* root@"${dest_file_host}":"${dest_file_path_root}""${date}"/"${db}"/ # 新实例刷新 mysql -h"${dest_db_host}" -u"${mysql_user}" -p"${mysql_password}" -e "USE "${db}"; ALTER TABLE "${tb}" IMPORT TABLESPACE;" 2> /dev/null done end_time=$(date +'%Y-%m-%d %H:%M:%S') end_timestamp=$(date +%s) elapsed_seconds=$((end_timestamp - start_timestamp)) hours=$((elapsed_seconds / 3600)) minutes=$(( (elapsed_seconds / 60) % 60)) seconds=$((elapsed_seconds % 60)) echo "Total time taken: $hours hours $minutes minutes $seconds seconds."
关键点:
mysql会话一旦关闭cfg、ibd文件就会自动删除,当文件比较大的时候,就需要考虑保持会话的存在直至数据传输完成