ITPub博客

首页 > 数据库 > MySQL > mysql亿级大表重构方案介绍

mysql亿级大表重构方案介绍

原创 MySQL 作者:caoyutingtjpu 时间:2017-05-11 16:40:53 0 删除 编辑

mysql亿级大表重构方案介绍

 作者:sylar版权所有[文章允许转载,但必须以链接方式注明源地址,否则追究法律责任.]

【情况简述】

   本文主要分享的博主将mysql生产环境上亿大表按照一定规则拆分成若干个小表并迁移的思路、实现方式、注意事项等等。

【背景说明】

   生产环境favourite表5.8亿,情况如下:

表名

表结构

rows

数据库版本

favourite

CREATE TABLE `favourite` (

  `id` int(11) NOT NULL AUTO_INCREMENT,

`user_id` int(11) NOT NULL,

`target_type` int(11) NOT NULL,

`target_id` int(11) NOT NULL,

`created_at` datetime NOT NULL,

`status` smallint(6) NOT NULL DEFAULT '0',

PRIMARY KEY (`id`),

  UNIQUE KEY `uniq_user_target` (`user_id`,`target_type`,`target_id`),

  KEY `idx_targetid` (`target_id`)

) ENGINE=InnoDB  DEFAULT CHARSET=utf8mb4

587312519

5.7.12


  下面sql因表的量级变的比较慢,已无法通过调整索引或调整sql进行优化:

SQL

time

SELECT count(1) AS count_1 FROM `favourite` WHERE `favourite`.target_id = 636 AND `favourite`.target_type = 1

4.7S

SELECT `favourite`.target_id AS `favourite_target_id` FROM `favourite` WHERE `favourite`.user_id = 338072 AND `favourite`.target_type = 0 AND `favourite`.status = 0 ORDER BY `favourite`.id DESC

2.25S

DELETE FROM favourite WHERE user_id = 17327373 AND target_id = 917 AND target_type = 1

0.9S

  为了业务响应比较快,决定拆分favourite表。经过业务沟通,user_id使用较为频繁,故通过user_id拆分,拆分规则根据user_id%1024打算到1024表,映射关系如下:

user_id%1024 =0  =>favourite_0000

user_id%1024 =1  =>favourite_0001

user_id%1024 =2  =>favourite_0002

……

user_id%1024 =1023  =>favourite_1023


注意:

1)拆分一定要根据业务情况来决定,不能一概而论!


【思路说明】

1、配置好canal ,canal是阿里开源的获取binlog信息的软件。从第一步开始到最后结束,canal一直不停获取binlog信息。

2、在不影响业务的数据库上(此处用的从库)将favourite导出成1024个表对应的文件

3、将导出备份文件导入生产环境

4、将canal获取的数据导入到1024个分表(一直进行直到结束)

5、待分表数据与原大表数据差不多时,在业务不繁忙时,切favourite业务读操作

6、切生产favourite写操作

7、待canal无新的记录产生,整个业务切换完毕

8、结束


注意:

1)使用canal获取binlog信息,注意参数设置为

    binlog_format=row

    binlog_row_image=full(默认是FULL,以防有些实例设置为minimal)

     binlog_row_image=minimal,此时主库进行delete from a where target_id =,在binlog记录的也是这样的操作,而如果target_id是唯一索引,则到了binlog记录的是delete from a where id(id是主键)。同样生产是delete favourite 是根据`user_id`,`target_type`,`target_id`,根据前面所说记录到binlog的是 delete from favourite where id,canal需要在业务发生切换时候记录变更的情况,可是获取的是删除的id,新的分表的id不会跟旧的favouriteid相同(因为旧的favourite的id已经到了8亿多,新的分表沿用旧表id是很浪费的情况)并不知道user_id是哪个,无法对新表进行同步。

2)服务先切读再切写的这个方式,在切换时间内会有一定数据误差的。比如用户在业务切换读操作的同时,往favourite表插入了一条数据;可是写操作没有切过来,数据落入旧的favourite表,用户就会发现异常影响用户的体验。这个情况直到canal将所有信息同步到新的分表后才解决。如果自己的业务无法接受这个情况,可以根据实际情况调整,不一定按照此方式切换服务。


【方案说明】

   根据上面思路,需要解决两个问题

1)如何配置canal。canal是阿里推出的获取binlog的开源产品,我们此次canal调用是java工程师帮忙写的代码,博主不懂java,故省略canal配置信息。

2)如何将favourite导出成1024个分表所需的数据,然后倒入指定库。

下面主要说明问题2实现的方式,一共有两种:

items

方案一

方案二

实现手段

mysqldump

mycat

拆分耗时

4.5Hour

2Hour

准备时间

3Hour,需要加函数索引

<1Hour,准备mycat环境和mycat对应的数据库

优点

不需要配置mycat环境

时间比方案一节省2Hour,导入目标环境后不需在初始化id

缺点

耗时太久、导入目标环境后还需要初始化id

需要熟悉mycat配置、分库规则

具体方式

Step1.在从库建立函数索引,耗时3Hour

Step2.在从库使用mysqldump的--where参数导出

Step3.导入目标库并初始化id

Step1.搭建mycat环境,并配置好相关规则

Step2.使用mysqldump备份文件

Step3.将备份文件导入mycat

Step4.在mycat对应库初始化id

Step5.将处理后的文件导入目标库

【方式一:mysqldump】

Step1.在从库建立函数索引,耗时3Hour

   alter table favourite  add `vis_user_id` int(11) GENERATED ALWAYS AS ((`user_id` % 1024)) STORED;

注意:

1)要在从库建立函数索引,影响会降低很多,如果能把让生产不访问该从库更好。确保生产环境访问该从库时没有select * from favourite where …..这样的命令

2)如果数据库版本低于5.7无法使用函数索引,那么step2.mysqldump备份一次开启4个并发进程,一次耗时230秒;如果有索引,则为30-60秒

 

Step2.在从库使用mysqldump的--where参数导出

思路:

1)使用--where=" user%1024=0001"导出成按拆分规则命名的文件,该例子对应文件名为0001.sql,一共会产生1024个这样的文件。
2)然后根据导出的文件名用sed命令替换表名(sed是shell命令)

具体脚本如下:


点击(此处)折叠或打开

  1. #查看log日志调用并发函数实现多线程备份
  2. function dumpAllTable()
  3. {
  4.         local schemaFile="${parallel_log}"
  5.         #最大的表先备份(因多进程并发,最短完成时间依赖于最大表的完成)
  6.         allTable=`cat $schemaFile | wc -l`
  7.         i_import=0
  8.         declare -a array_cmds
  9.         i_array=0
  10.         while read file; do
  11.                 i_import=`expr $i + 1`
  12.                 array_cmds[i_array]="${file}"
  13.                 i_array=`expr ${i_array} + 1`
  14.         done < $schemaFile
  15.         execConcurrency "${threadsNum}" "${array_cmds[@]}"
  16. }


  17. #并发函数
  18. function execConcurrency()
  19. {
  20.         #并发数据量
  21.         local thread=$1
  22.         #并发命令
  23.         local cmd=$2
  24.         #定义管道,用于控制并发线程
  25.         tmp_fifofile="/tmp/$$.fifo"
  26.         mkfifo $tmp_fifofile
  27.         #输入输出重定向到文件描述符6
  28.         exec 6<>$tmp_fifofile
  29.         rm -f $tmp_fifofile
  30.         #向管道压入指定数据的空格
  31.         for ((i=0;i<$thread;i++)); do
  32.                 echo
  33.         done >&6
  34.         #遍历命令列表
  35.         while [ "$cmd" ]; do
  36.                 #从管道取出一个空格(如无空格则阻塞,达到控制并发的目的)
  37.                 read -u6
  38.                 #命令执行完后压回一个空格
  39.                 { eval $2;echo >&6; } & #> /dev/null 2>&1 &
  40.                 shift
  41.                 cmd=$2
  42.         done
  43.         #等待所有的后台子进程结束
  44.         wait
  45.         #关闭df6
  46.         exec 6>&-
  47. }

  48. function instance_log()
  49. {
  50.          if [[ -f "${parallel_log}" ]];then
  51.                 rm -rf "${parallel_log}"
  52.          fi;
  53.          for i in $(seq -w 0 1023)
  54.             do
  55.             echo "${MYSQLDUMP} -u${user} -p${password} -S ${socket} --default-character-set=utf8mb4 -c --set-gtid-purged=OFF --skip-add-locks --skip-quick --no-create-db --log-error=${backupdir}/cyt.log --skip-add-drop-table ${database} ${tablename} --where=\" vis_${columnname}=${i} \"> ${backupdir}/${i}.sql " >>${parallel_log}
  56.          done
  57.          dumpAllTable
  58.          mv ${parallel_log} ${parallel_log}_bak
  59. }

  60. function change()
  61. {
  62.          if [[ -f "${parallel_log}" ]];then
  63.                 rm -rf "${parallel_log}"
  64.          fi;
  65.          for i in $(seq -w 0 1023)
  66.              do
  67.              echo "sed -i 's/\`${tablename}\`/\`${tablename}_${i}\`/g' ${backupdir}/${i}.sql" >>${parallel_log}
  68.          done
  69.          dumpAllTable
  70.          mv ${parallel_log} ${parallel_log}_bak_2
  71. }

  72. function main()
  73. {
  74.          MYSQLDUMP=""
  75.          user=""
  76.          password=""
  77.          socket=""
  78.          columnname="user_id"
  79.          tablename="favourite"
  80.          database="kuaikan"
  81.          backupdir="/data/backup"
  82.          parallel_log=""
  83.          threadsNum=2
  84.          instance_log
  85.          change
  86. }

  87. main

      在加了函数索引情况下,开启4个并行进行dump,一次花费60秒左右,一共1024个文件:1024/4*60s/3600s=4.3hour 时间长达4个半小时。(根据自己机器IO来调整并行度,不是并行越高,速度越快,博主环境在开启6个并行后,预计耗时5个多小时。开启2个并行耗时跟4个并行差不多)

Step3.导入目标库并初始化id

    注意新表的id都比较大,需要重新初始化下id,这个表的created_at跟id顺序是一样的,故按照created_at排序,初始化方式为:

i)alter table favourite_XXXX drop column id ,drop column vis_user_id,drop key uniq_user_target ,order by created_at ;

#因为需要初始化id,所以需要先删除id,同时删除唯一索引是为了后面语句order by created_at。因为InnoDB会对主键建立聚簇索引,如果没有主键,InnoDB会用一个具有唯一且非空值的索引来代替,此时就是UNIQUE KEY `uniq_user_target`。一般来说,DBMS都会以聚簇索引的形式来存储实际的数据,它是其它二级索引的基础。在聚簇索引中,索引树的叶级页包含实际的数据:记录的索引顺序与物理顺序相同。在非聚簇索引中,叶级页指向表中的记录:记录的物理顺序与逻辑顺序没有必然的联系。所以为了让表按照created_at排序,需要删除上面的唯一键,否则执行下面语句无法生效。

ii)alter table favourite_XXXX  add id  int(11) NOT NULL AUTO_INCREMENT first ,add primary key (id), add UNIQUE KEY uniq_user_target(user_id,target_type,target_id)

#初始化id,补回索引。

iii)具体脚本如下


点击(此处)折叠或打开

  1. function import_log()
  2. {
  3.          if [[ -e "${log_1}" ]];then
  4.             rm -rf ${log_1};
  5.          fi;
  6.          for filename in `du -sk ${BACKUP_DIR}/*.sql | sort -nr |awk '{print $2}'`
  7.             do
  8.             
  9.             echo "mysql -u${DB_USER} -p${DB_PASSWORD} --port=${port} --socket=${socket} --host=${host} -A ${database} --tee=/data/pat0405.log -e \"source ${filename}\"">>${log_1};
  10.           done
  11.           import=`echo $?`
  12.           if [[ "${import}" -eq 0 ]];then
  13.              echo "import_log successfully" `date "+%Y-%m-%d %H:%M:%S"`>>${BACKUP_DIR}/import.log 2>&1
  14.           else
  15.              echo "import_log failed" `date "+%Y-%m-%d %H:%M:%S"`>>${BACKUP_DIR}/import.log 2>&1
  16.           fi
  17. }

  18. function modify_column_log()
  19. {
  20.          if [[ -e "${log_2}" ]];then
  21.             rm -rf ${log_2};
  22.          fi;
  23.          for i in $(seq -w 0 1023 )
  24.              do
  25.              echo "mysql -u${DB_USER} -p${DB_PASSWORD} --port=${port} --socket=${socket} --host=${host} -A ${database} --tee=/data/pat0405.log -e \" alter table favourite_${i} drop column id ,drop key uniq_user_target ,order by created_at ;alter table favourite_${i} add id int(11) NOT NULL AUTO_INCREMENT first ,add primary key (id), add UNIQUE KEY uniq_user_target(user_id,target_type,target_id)\" " >>${log_2}
  26.          done
  27.          modify=`echo $?`
  28.          if [[ "${modify}" -eq 0 ]];then
  29.              echo "modify_column_log successfully" `date "+%Y-%m-%d %H:%M:%S"`>>${BACKUP_DIR}/import.log 2>&1
  30.          else
  31.              echo "modify_column_log failed" `date "+%Y-%m-%d %H:%M:%S"`>>${BACKUP_DIR}/import.log 2>&1
  32.          fi
  33. }



  34. #调用函数log,查看log日志调用并发函数实现多线程备份
  35. function dumpAllTable()
  36. {
  37.         local schemaFile="$1"
  38.         local threadsNum="$2"
  39.         #最大的表先备份(因多进程并发,最短完成时间依赖于最大表的完成)
  40.         allTable=`cat $schemaFile | wc -l`
  41.         i_import=0
  42.         declare -a array_cmds
  43.         i_array=0
  44.         while read file; do
  45.                 i_import=`expr $i + 1`
  46.                 array_cmds[i_array]="${file}"
  47.                 i_array=`expr ${i_array} + 1`
  48.         done < ${schemaFile}
  49.         execConcurrency "${threadsNum}" "${array_cmds[@]}"
  50.         delta_ret_val=`echo $?`
  51.         if [[ "${delta_ret_val}" -eq 0 ]];then
  52.            echo "${schemaFile} successfully" `date "+%Y-%m-%d %H:%M:%S"`>>${BACKUP_DIR}/import.log 2>&1
  53.         else
  54.            echo "${schemaFile} failed" `date "+%Y-%m-%d %H:%M:%S"`>>${BACKUP_DIR}/import.log 2>&1
  55.         fi
  56. }


  57. #并发函数
  58. function execConcurrency()
  59. {
  60.         #并发数据量
  61.         local thread=$1
  62.         #并发命令
  63.         local cmd=$2
  64.         #定义管道,用于控制并发线程
  65.         tmp_fifofile="/tmp/$$.fifo"
  66.         mkfifo $tmp_fifofile
  67.         #输入输出重定向到文件描述符6
  68.         exec 6<>$tmp_fifofile
  69.         rm -f $tmp_fifofile
  70.         #向管道压入指定数据的空格
  71.         for ((i=0;i<$thread;i++)); do
  72.                 echo
  73.         done >&6
  74.         #遍历命令列表
  75.         while [ "$cmd" ]; do
  76.                 #从管道取出一个空格(如无空格则阻塞,达到控制并发的目的)
  77.                 read -u6
  78.                 #命令执行完后压回一个空格
  79.                 { eval $2;echo >&6; } & #> /dev/null 2>&1 &
  80.                 shift
  81.                 cmd=$2
  82.         done
  83.         #等待所有的后台子进程结束
  84.         wait
  85.         #关闭df6
  86.         exec 6>&-
  87.         exec_con=`echo $?`
  88.         if [[ "${exec_con}" -eq 0 ]];then
  89.            echo " execConcurrency successfully" `date "+%Y-%m-%d %H:%M:%S"`>>${BACKUP_DIR}/import.log 2>&1
  90.         else
  91.            echo "execConcurrency failed" `date "+%Y-%m-%d %H:%M:%S"`>>${BACKUP_DIR}/import.log 2>&1
  92.         fi

  93. }

  94. function instance()
  95. {
  96.          import_log
  97.          modify_column_log
  98.          echo "-----端口号为"$port"的mysql实例开始按表并发倒入:开始时间为"`date "+%Y-%m-%d %H:%M:%S"`
  99.          #调用执行函数
  100.          dumpAllTable ${log_1} 1
  101.          dumpAllTable ${log_2} 6
  102.          END=`date "+%Y-%m-%d %H:%M:%S"`
  103.          END_T=`date -d "$END" +%s`
  104.          TIME_INVENTAL_M=$[($END_T-$BEGIN_T)/60]
  105.          TIME_INVENTAL_S=$[($END_T-$BEGIN_T)%60]
  106.              

  107. }

  108. #主函数
  109. function main()
  110. {
  111.         #IP地址
  112.         host=""
  113.         port=
  114.         DATE=`date +%F`
  115.         socket=""
  116.         DB_USER=''
  117.         #数据库用户对应的密码
  118.         DB_PASSWORD=''
  119.         #记录开始的时间
  120.         BEGIN=`date "+%Y-%m-%d %H:%M:%S"`
  121.         BEGIN_T=`date -d "$BEGIN" +%s`
  122.         echo '--------------开始按表并发导入:开始时间为 '$BEGIN
  123.         BACKUP_DIR="/data/backup"
  124.         log_1=""
  125.         log_2=""
  126.         database="kk_favourite"
  127.         #调用instance函数
  128.         instance
  129.         succ=`echo $?`
  130.         if [[ "${succ}" -eq 0 ]]&&[[ "${modify}" -eq 0 ]]&&[[ "${import}" -eq 0 ]];then
  131.            echo "MySQL DB Instance:${port} import data sucessfully,cost time:${TIME_INVENTAL_M} min ${TIME_INVENTAL_S} s" >>${BACKUP_DIR}/import.log 2>&1
  132.         else
  133.            echo "MySQL DB Instance:${port} import data failed,cost time:${TIME_INVENTAL_M} min ${TIME_INVENTAL_S} s" >>${BACKUP_DIR}/import.log 2>&1
  134.         fi
  135. }

  136. main "$1" "$2"

        该操作如果导入的实例有其他数据库,一定要在业务不繁忙的时候导入,否则影响生产环境。这是由于dump生成的一条insert相当于一次性插入2-3万行,会占用生产环境的资源。

         此次处理耗时1小时左右(2个并行)

【方案二:mycat分表】

Step1.搭建mycat环境,并配置好相关规则

  mycat分表是一个逻辑库对应一个分表,所以需要创建1024个逻辑库对应1024个分表,考虑到机器IO,打算将1024个库分到4个实例(数据库均是单实例,如果有条件,可以分到更多的机器上,速度会快些)

  需要修改的配置文件:server.xml、schema.xml、rule.xml及其对应的partition-hash-int.txt。具体修改请查看附件conf.zip

conf.zip


注意:

1)需要提前创建1024个逻辑库

2)这里是根据方案一提到的函数索引对应的虚拟列vis_user_id来分的,这样可以直接用mycat的枚举分库,如果不想用虚拟列,可以用mycat hash来划分,这个对于数值划分方式等同于user%1024,这个详情参考mycat权威指南

3)需要提前在四个逻辑库里创建好用于mycat访问的数据库用户

4)在创建完1024个逻辑库后,登入mycat,再创建favourite表,这样每个逻辑库都有该表

 

Step2.使用mysqldump备份文件

   为了能快速导入mycat,故根据mycat分成4个实例规则(可以有误差,不一定要完全一样),导出4份不同数据,以便可以同时4份文件灌入mycat

使用mysqldump导出4个文件,以下备份同时进行,耗时20分钟:

  • mysqldump -u$USERNAME -p$PASSWORD -S $SOCKET --default-character-set=utf8mb4 -c --set-gtid-purged=OFF --skip-add-locks --skip-quick  --no-create-db --log-error=/data/cyt0324.log --skip-add-drop-table    kuaikan favourite --where=" mod(user_id,1024)<256 "     >   /data/favourite_256.sql
  • mysqldump ……………………  --where=" mod(user_id,1024)>=256 and mod(user_id,1024) <512   "     >   /data/favourite_512.sql
  • mysqldump ……………………  --where=" mod(user_id,1024)>=512 and mod(user_id,1024)< 768 "     >   /data/favourite_768.sql
  • mysqldump ……………………  --where=" mod(user_id,1024) >=768"     >   /data/favourite_1024.sql

注意:

1)请在从库或业务不去访问的数据库上进行备份

2)上面设置的参数请根据实际情况调整,一定要加上-c   --skip-add-locks参数,否则导入mycat会异常

Step3.将备份文件导入mycat

   将步骤2导出的四个备份文件同时灌入mycat,整个耗费时间不足90分钟。

注意:

1)请将该操作在后台执行,可以用screen命令来实现。因为mycat是长连接,即使中途断掉后,后面的语句可能还会继续执行,以防出现这样情况,请放到后台执行

 

Step4.在mycat对应库初始化id

此处耗时30分钟

1)在1024分库上初始化id,去掉虚拟列,具体脚本如下


点击(此处)折叠或打开

  1. function dumpAllTable()
  2. {
  3.         local schemaFile="${parallel_log}"
  4.         #最大的表先备份(因多进程并发,最短完成时间依赖于最大表的完成)
  5.         allTable=`cat $schemaFile | wc -l`
  6.         i_import=0
  7.         declare -a array_cmds
  8.         i_array=0
  9.         while read file; do
  10.                 i_import=`expr $i + 1`
  11.                 array_cmds[i_array]="${file}"
  12.                 i_array=`expr ${i_array} + 1`
  13.         done < $schemaFile
  14.         execConcurrency "${threadsNum}" "${array_cmds[@]}"
  15. }


  16. #并发函数
  17. function execConcurrency()
  18. {
  19.         #并发数据量
  20.         local thread=$1
  21.         #并发命令
  22.         local cmd=$2
  23.         #定义管道,用于控制并发线程
  24.         tmp_fifofile="/tmp/$$.fifo"
  25.         mkfifo $tmp_fifofile
  26.         #输入输出重定向到文件描述符6
  27.         exec 6<>$tmp_fifofile
  28.         rm -f $tmp_fifofile
  29.         #向管道压入指定数据的空格
  30.         for ((i=0;i<$thread;i++)); do
  31.                 echo
  32.         done >&6
  33.         #遍历命令列表
  34.         while [ "$cmd" ]; do
  35.                 #从管道取出一个空格(如无空格则阻塞,达到控制并发的目的)
  36.                 read -u6
  37.                 #命令执行完后压回一个空格
  38.                 { eval $2;echo >&6; } & #> /dev/null 2>&1 &
  39.                 shift
  40.                 cmd=$2
  41.         done
  42.         #等待所有的后台子进程结束
  43.         wait
  44.         #关闭df6
  45.         exec 6>&-
  46. }

  47. function instance_log()
  48. {
  49.          if [[ -f "${parallel_log}" ]];then
  50.                 rm -rf "${parallel_log}"
  51.          fi;
  52.          for i in $(seq -w 0 1023 )
  53.              do
  54.              echo "mysql -u$user -p$password -h$IP -P ${port} -A ${database}_${i} -e \" alter table ${tablename} drop column ${columnname}, drop column id ,drop key user_target;alter table ${tablename} order by created_at ;alter table ${tablename} add id int(11) NOT NULL AUTO_INCREMENT first ,add primary key (id); alter table ${tablename} add UNIQUE KEY uniq_user_target(user_id,target_type,target_id)\" " >>${parallel_log}
  55.          done
  56.          dumpAllTable
  57.          mv ${parallel_log} ${parallel_log}_bak
  58. }



  59. function main()
  60. {
  61.          user=""
  62.          password=""
  63.          port=""
  64.          IP=""
  65.          columnname="vis_user_id"
  66.          tablename="favourite"
  67.          database="favourite"
  68.          backupdir="/data/backup"
  69.          parallel_log=""
  70.          threadsNum=6
  71.          instance_log
  72. }

  73. main


2)将1024个分库对应的表并行dump,并使用sed根据库名将表名favourite改为favourite_XXXX,具体脚步如下


点击(此处)折叠或打开

  1. #查看log日志调用并发函数实现多线程备份
  2. function dumpAllTable()
  3. {
  4.         local schemaFile="${parallel_log}"
  5.         #最大的表先备份(因多进程并发,最短完成时间依赖于最大表的完成)
  6.         allTable=`cat $schemaFile | wc -l`
  7.         i_import=0
  8.         declare -a array_cmds
  9.         i_array=0
  10.         while read file; do
  11.                 i_import=`expr $i + 1`
  12.                 array_cmds[i_array]="${file}"
  13.                 i_array=`expr ${i_array} + 1`
  14.         done < $schemaFile
  15.         execConcurrency "${threadsNum}" "${array_cmds[@]}"
  16. }


  17. #并发函数
  18. function execConcurrency()
  19. {
  20.         #并发数据量
  21.         local thread=$1
  22.         #并发命令
  23.         local cmd=$2
  24.         #定义管道,用于控制并发线程
  25.         tmp_fifofile="/tmp/$$.fifo"
  26.         mkfifo $tmp_fifofile
  27.         #输入输出重定向到文件描述符6
  28.         exec 6<>$tmp_fifofile
  29.         rm -f $tmp_fifofile
  30.         #向管道压入指定数据的空格
  31.         for ((i=0;i<$thread;i++)); do
  32.                 echo
  33.         done >&6
  34.         #遍历命令列表
  35.         while [ "$cmd" ]; do
  36.                 #从管道取出一个空格(如无空格则阻塞,达到控制并发的目的)
  37.                 read -u6
  38.                 #命令执行完后压回一个空格
  39.                 { eval $2;echo >&6; } & #> /dev/null 2>&1 &
  40.                 shift
  41.                 cmd=$2
  42.         done
  43.         #等待所有的后台子进程结束
  44.         wait
  45.         #关闭df6
  46.         exec 6>&-
  47. }

  48. function instance_log()
  49. {
  50.          if [[ -f "${parallel_log}" ]];then
  51.                 rm -rf "${parallel_log}"
  52.          fi;
  53.          for i in $(seq -w 0 1023 )
  54.              do
  55.              echo "${MYSQLDUMP} -u${user} -p${password} -h$IP -P $port --default-character-set=utf8mb4 -c --set-gtid-purged=OFF --skip-add-locks --skip-quick --no-create-db --log-error=${backupdir}/cyt.log --skip-add-drop-table ${database}_${i} ${tablename} > ${backupdir}/${i}.sql " >>${parallel_log}
  56.          done
  57.          dumpAllTable
  58.          mv ${parallel_log} ${parallel_log}_bak
  59. }

  60. function change()
  61. {
  62.          if [[ -f "${parallel_log}" ]];then
  63.                 rm -rf "${parallel_log}"
  64.          fi;
  65.          for i in $(seq -w 0 1023 )
  66.              do
  67.              echo "sed -i 's/\`${tablename}\`/\`${tablename}_${i}\`/g' ${backupdir}/${i}.sql" >>${parallel_log}
  68.          done
  69.          dumpAllTable
  70.          mv ${parallel_log} ${parallel_log}_bak_2
  71. }

  72. function main()
  73. {
  74.          MYSQLDUMP="mysqldump"
  75.          user=""
  76.          password=""
  77.          port=""
  78.          IP=""
  79.          columnname="user_id"
  80.          tablename="favourite"
  81.          database="favourite"
  82.          backupdir="/data/backup"
  83.          parallel_log=""
  84.          threadsNum=6
  85.          instance_log
  86.          change
  87. }

  88. main


 Step5.将处理后的文件导入目标库

此处耗时30分钟

将处理后的备份文件导入生产新库,并行导入,并行度最好不要超过3,因为dump导出后insert一次是3万左右,并行度太高,机器IO会hold不住,脚本如下:


点击(此处)折叠或打开

  1. function instance()
  2. {
  3.              log
  4.              echo "-----端口号为"$port"的mysql实例开始按表并发倒入:开始时间为"`date "+%Y-%m-%d %H:%M:%S"`
  5.              #调用执行函数
  6.              dumpAllTable

  7. }



  8. #将要备份的单表从大到小输出到日志里面
  9. function log()
  10. {
  11.             BACKUP_DIR="/data/backup/"
  12.             #过滤掉MySQL自带的DB
  13.             if [ -e ${BACKUP_DIR}/cyt.log ];
  14.                then rm -rf ${BACKUP_DIR}/cyt.log;
  15.             fi;
  16.             for filename in `du -sk ${BACKUP_DIR}/*.sql | sort -nr |awk '{print $2}'`
  17.                do
  18.                a="kk_favourite"
  19.                echo "mysql -u${DB_USER} -p${DB_PASSWORD} --socket=${socket} --host=${host} -A ${a} --tee=/data/pat.log -e \"source ${filename}\"">>$BACKUP_DIR/cyt.log;
  20.              done
  21. }

  22.    

  23. #调用函数log,查看log日志调用并发函数实现多线程备份
  24. function dumpAllTable()
  25. {
  26.         local schemaFile="${BACKUP_DIR}/cyt.log"
  27.         #最大的表先备份(因多进程并发,最短完成时间依赖于最大表的完成)
  28.         allTable=`cat $schemaFile | wc -l`
  29.         i_import=0
  30.         declare -a array_cmds
  31.         i_array=0
  32.         while read file; do
  33.                 i_import=`expr $i + 1`
  34.                 array_cmds[i_array]="${file}"
  35.                 i_array=`expr ${i_array} + 1`
  36.         done < ${BACKUP_DIR}/cyt.log
  37.         execConcurrency "${threadsNum}" "${array_cmds[@]}"
  38.         delta_ret_val=`echo $?`
  39. }





  40. #并发函数
  41. function execConcurrency()
  42. {
  43.         #并发数据量
  44.         local thread=$1
  45.         #并发命令
  46.         local cmd=$2
  47.         #定义管道,用于控制并发线程
  48.         tmp_fifofile="/tmp/$$.fifo"
  49.         mkfifo $tmp_fifofile
  50.         #输入输出重定向到文件描述符6
  51.         exec 6<>$tmp_fifofile
  52.         rm -f $tmp_fifofile
  53.         #向管道压入指定数据的空格
  54.         for ((i=0;i<$thread;i++)); do
  55.                 echo
  56.         done >&6
  57.         #遍历命令列表
  58.         while [ "$cmd" ]; do
  59.                 #从管道取出一个空格(如无空格则阻塞,达到控制并发的目的)
  60.                 read -u6
  61.                 #命令执行完后压回一个空格
  62.                 { eval $2;echo >&6; } & #> /dev/null 2>&1 &
  63.                 shift
  64.                 cmd=$2
  65.         done
  66.         #等待所有的后台子进程结束
  67.         wait
  68.         #关闭df6
  69.         exec 6>&-
  70. }


  71. #主函数
  72. function main()
  73. {
  74.         #获取本地IP地址
  75.         host=""
  76.         port=
  77.         DATE=`date +%F`
  78.         socket=""
  79.         DB_USER=''
  80.         #数据库用户对应的密码
  81.         DB_PASSWORD=''
  82.         #记录开始的时间
  83.         BEGIN=`date "+%Y-%m-%d %H:%M:%S"`
  84.         BEGIN_T=`date -d "$BEGIN" +%s`
  85.         echo '--------------开始按表并发备份:开始时间为 '$BEGIN
  86.         #设置并发备份的线程数
  87.         threadsNum=8
  88.         #调用instance函数
  89.         instance
  90.         echo '--------------backup all database successfully!!!结束时间:' `date "+%Y-%m-%d %H:%M:%S"`
  91. }

  92. main

【小节】

由于方案二速度比方案一节省至少2小时以上的时间,且导入目标库后无需其他处理,故生产环境迁移使用的方案二



来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/31324175/viewspace-2138921/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2016-05-13

  • 博文量
    19
  • 访问量
    67026