簡介
基本的需求是這樣的:現在有兩個服務器host1與host2,里面的MySQL服務分別存着兩個數據庫db1與db2,假設我想將數據庫db1的A表中的數據同步到數據庫db2的B中,而且A與B的表結構完全一樣!
特別要注意的要求是:必須忽略主鍵沖突的記錄!也就是說,只有當A中存在的記錄但是在B中不存在時才往數據庫B中insert,如果A與B都有的數據還是以B中的數據為准!
相信大家看到題目中的業務問題描述首先想到的是數據庫的主從同步以及安裝各種第三方插件的方案,但是由於業務原因不支持上面提到的方式。
另外既有的方式是我用python腳本實現的,但是隨着業務數據量的增加python腳本的執行效率肯定不如shell,所以想着在效率瓶頸來臨之前優化一下既有的實現方案。
本文主要介紹一下使用shell腳本執行MySQL命令實現的思路以及具體shell腳本的實現。
本地初始化測試
首先在本地搭建一下測試的環境,比較簡單,只需要2個不同數據庫相同的表就可以了,因為我是在本地測試,所以host1與host2的ip都是127.0.0.1。
我在本地分別創建了兩個數據庫:students1與students2,兩張相同的表叫s_test。
students1中有3條數據:
students2中有1條數據,且這條數據的主鍵與students1中的某一條一樣,數據不一樣:
現在要做的是:將students1中主鍵為2跟3的數據同步到students2中(因為students2中沒有這兩條數據),但是得保留students2主鍵為1的那條數據!
使用mysqldump命令實現的思路與過程詳解
mysqldump命令主要用來備份與同步數據庫,我現在的實現思路是先將A表的數據dump下來,然后再使用dump下來的數據去同步B表。
這里特別提醒一點:一定要只dump表A的數據,而不是結構+數據!首先表A與表B的表結構本身是一模一樣的!另外如果同步了結構+數據的話生成的sql文件開頭會有一個drop目標表的語句!這樣的話B中的數據就全沒了!不符合一開始的要求!
從表A中dump數據的語句
mysqldump -h 127.0.0.1 -P 3306 -uroot -p123 -t students1 s_test --insert-ignore=TRUE --where="age>12" > s_test.sql
注意:
(1)這里指定 -t 參數的意思是只導出數據不導出結構;
(2)--insert_ignore=TRUE 表示在導入數據時如果遇到主鍵沖突的情況就忽略這條記錄;
(3)--where表示篩選出哪些記錄導出。
看一下導出來的s_test.sql文件:
LOCK TABLES `s_test` WRITE; /*!40000 ALTER TABLE `s_test` DISABLE KEYS */; INSERT IGNORE INTO `s_test` VALUES (1,'whw',22),(2,'naruto',23),(3,'sasuke',24); /*!40000 ALTER TABLE `s_test` ENABLE KEYS */; UNLOCK TABLES;
注意這里是 insert ignore 語法。
簡單的shell腳本
#!/bin/bash # 這里是本地測試 實際中的host是數據庫A所在的host與port mysqldump -h 127.0.0.1 -P 3306 -uroot -p123 -t students1 s_test --insert-ignore=TRUE --where="age>12" > s_test.sql # 本地測試 實際中的host是數據庫B所在的host與port mysql -h 127.0.0.1 -P 3306 -uroot -p123 students2 < s_test.sql # 刪掉臨時生成的sql文件 rm -rf ./s_test.sql
測試結果
執行完上面的腳本后我們再看看students2中的結果:
我們可以看到,students2中原來的數據沒有被修改,只是把他之前沒有的數據insert進去了。
腳本完善 ***
基本的實現思路就是這樣,只不過上面的腳本着實很low,接下來加上了日志與多表同步的處理。。。
#!/bin/bash source /etc/profile # 實際中可以換成source數據庫的信息 sou_mysql_host='127.0.0.1' sou_mysql_port='3306' sou_mysql_user='root' sou_mysql_pwd='123' sou_mysql_db_name='students1' #僅支持一個數據庫 sou_mysql_table_name=( # 兩張數據庫中相同的表的名字 s_test # 如果有其他表的話再添加就好了!可以同步多張相同的表! # s_test2 ) # 實際中可以換成destination數據庫的信息 des_mysql_host='127.0.0.1' des_mysql_port='3306' des_mysql_user='root' des_mysql_pwd='123' des_mysql_db_name='students2' #僅支持一個數據庫 #記得先同步表結構 不然會失敗 # 存放sql文件與日志的目錄 sync_db_dir='./syncdata/' log_file='./logs/mysql_to_mysql.log' [ -d ./logs ] || mkdir ./logs [ -d ./syncdata ] || mkdir ./syncdata func_writeLog(){ local getString=$1 if [ $? -eq 0 ];then echo "TIME:$(date +"%Y%m%d_%H:%M"),The ${getString}" echo "TIME:$(date +"%Y%m%d_%H:%M"),The ${getString}" >>${log_file} else echo "TIME:$(date +"%Y%m%d_%H:%M"),The ${getString}" echo "TIME:$(date +"%Y%m%d_%H:%M"),The ${getString}" >>${log_file} fi } func_sourceBackupCmd(){ for tabname in $(echo ${sou_mysql_table_name[@]}|sed 's/ / /g');do /usr/local/mysql/bin/mysqldump -h${sou_mysql_host} -P${sou_mysql_port} -u${sou_mysql_user} -p${sou_mysql_pwd} \ --insert-ignore=true \ --default-character-set=utf8 \ -t ${sou_mysql_db_name} ${tabname} >${sync_db_dir}/${sou_mysql_db_name}_${tabname}.sql func_writeLog "${sou_mysql_db_name}_${tabname}.sql backup ok" done } func_syncMysqlCmd(){ for sqlname in $(ls ${sync_db_dir}/*.sql);do /usr/local/mysql/bin/mysql -h${des_mysql_host} -P${des_mysql_port} -u${des_mysql_user} -p${des_mysql_pwd} \ --default-character-set=utf8 ${des_mysql_db_name} < ${sqlname} func_writeLog "${sqlname} load ok" [ $? -eq 0 ] && rm -f ${sqlname} func_writeLog "${sqlname} deleted ok" done } main(){ func_sourceBackupCmd; func_syncMysqlCmd; } main