mysqlbinlog 數據恢復

Mysqlbinlog 數據恢復


mysqlbinlog 支持三種模式:

  • row level

  • 缺點:

    row level模式下,所有的執行的語句當記錄到日誌中的時候,都將以每行記錄的修改來記錄,這樣可能會產生大量的日誌內容,比如有這樣一條update語句:update product set user = ‘b’ id = ‘1’,執行之後,日誌中記錄的不是這條update語句所對應額事件(MySQL以事件的形式來記錄bin-log日誌),而是這條語句所更新的每一條記錄的變化情況,這樣就記錄成很多條記錄被更新的很多個事件。自然,bin-log日誌的量就會很大。尤其是當執行alter table之類的語句的時候,產生的日誌量是驚人的。因爲MySQL對於alter table之類的表結構變更語句的處理方式是整個表的每一條記錄都需要變動,實際上就是重建了整個表。那麼該表的每一條記錄都會被記錄到日誌中。

  • 優點:

    row level模式下,bin-log中可以不記錄執行的sql語句的上下文相關的信息,僅僅只需要記錄那一條記錄被修改了,修改成什麼樣了。所以row level的日誌內容會非常清楚的記錄下每一行數據修改的細節。且不會出現某些特定情況下的存儲過程,或function,以及 trigger的調用和觸發無法被正確複製的問題。

  • Statement Level

  • 缺點

   由於他是記錄的執行語句,所以,爲了讓這些語句在slave端也能正確執行,那麼他還必須記錄每條語句在執行的時候的一些相關信息,也就是上下文信息,以保證所有語句在slave端杯執行的時候能夠得到和在master端執行時候相同的結果。另外就是,由於MySQL現在發展比較快,很多的新功能不斷             的加入,使MySQL得複製遇到了不小的挑戰,自然複製的時候涉及到越複雜的內容,bug也就越容易出現。在statement level下,目前已經發現的就有不少情況會造成MySQL的複製出現問題,主要是修改數據的時候使用了某些特定的函數或者功能的時候會出現,比如:sleep()函數在有些版本中就不能           真確複製,在存儲過程中使用了last_insert_id()函數,可能會使slave和master上得到不一致的id等等。由於row level是基於每一行來記錄的變化,所以不會出現類似的問題。

  • 優點

    statement level下的優點首先就是解決了row level下的缺點,不需要記錄每一行數據的變化,減少bin-log日誌量,節約IO,提高性能。因爲他只需要記錄在Master上所執行的語句的細節,以及執行語句時候的上下文的信息。

  • Mixed

  • 說明

        Mixed模式,可以理解爲是前兩種模式的結合。
        Mixed模式下,MySQL會根據執行的每一條具體的sql語句來區分對待記錄的日誌形式,也就是在Statement和Row之間選擇一種。
        新版本中的Statment level還是和以前一樣,僅僅記錄執行的語句。而新版本的MySQL中隊row level模式也被做了優化,並不是所有的修改都會以row level來記錄,像遇到表結構變更的時候就會以statement模式來記錄,如果sql語句確實就是update或者delete等修改數據的語句,那麼還是會記錄所有行            的變更。


上述配置可以在/etc/my.cnf 下面進行配置,如下:

log-bin=mysql-bin
#binlog_format=”STATEMENT”
#binlog_format=”ROW”
binlog_format=”MIXED

也可以動態修改

mysql> SET SESSION binlog_format = ‘STATEMENT’;
mysql> SET SESSION binlog_format = ‘ROW’;
mysql> SET SESSION binlog_format = ‘MIXED’;
mysql> SET GLOBAL binlog_format = ‘STATEMENT’;
mysql> SET GLOBAL binlog_format = ‘ROW’;
mysql> SET GLOBAL binlog_format = ‘MIXED’;


配置完成後可使用如下語句查看狀態

show variables like '%binlog_format%';


基於mysqlbinlog 語句(statement)的恢復


  1. 確定需要恢復數據的時間和位置

    wKioL1dVP2OTPLbiAABirCGXPRc654.png-wh_50

    mysqlbinlog mysql-bin.000002 |more

    找出需要恢復的時間和位置


  2. 使用mysqlbinlog 已確定的時間恢復出語句文件

    可能有多個 mysql-bin.00000x 文件 第一個文件需要確定開始時間,最後一個文件需要確定結束時間。

    mysqlbinlog  --start-date="2016-06-06 12:30:00" --stop-date="2016-06-06 18:00:00" mysql-bin.000002 >rollbak0606_01.sql、


    --start-positon="50" //指定從50位置開始 

    --stop-postion="100"//指定到100位置結束


  1. 處理語句文件並恢復

    mysqlbinlog中包含了所有庫和表的sql語句。如需要區分庫可進行過濾:

    grep "insert into xxx" rollbak0606_01.sql >>rollbak0606_del.sql  

    

    導入處理後的文件 

    mysql -uroot -p'xxxx' --default-character-set=utf8 < rollbak0606_del.sql

    或者

    source 'filePath'

    基於mysqlbinlog 行(row)的恢復




  1. 確定恢復數據的時間和位置

    和基於語句的方式不同。基於行的binlog 使用mysqlbinlog 查看的時候存在亂碼問題無法清晰的確定時間。如下圖:

    wKioL1dVR6mxFWp3AAB-AyWwJKw126.png-wh_50

    因此要使用

mysqlbinlog  --base64-output=decode-rows -v --start-date='2016-06-06 14:00:00' --stop-date='2016-06-06 14:20:00'  /opt/mysql/log/mysql-bin.000001 >/opt/mysql_bak/mysqlbinlog_0606.sql


--base64-output=decode-rows 基於行的binlog

-v 用於重構基於行的sql


B. 恢復

   mysqlbinlog  --start-date="2016-06-06 16:30:00" --stop-date="2016-06-06 17:00:00" --start-positon="50"   --stop-postion="100"  mysql_bin.000001 |mysql -uroot -p123456

    --start-positon="50" //指定從50位置開始 

    --stop-postion="100"//指定到100位置結束 


C. 也可以轉換爲基於語句的複製


這裏需要一個perl 腳本。感謝提供腳本的大神。

#!/usr/lib/perl -w

use strict;
use warnings;

use Class::Struct;
use Getopt::Long qw(:config no_ignore_case);                    # GetOption
# register handler system signals
use sigtrap 'handler', \&sig_int, 'normal-signals';

# catch signal
sub sig_int(){
    my ($signals) = @_;
    print STDERR "# Caught SIG$signals.\n";
    exit 1;
}

my %opt;
my $srcfile;
my $host = '127.0.0.1';
my $port = 3306;
my ($user,$pwd);
my ($MYSQL, $MYSQLBINLOG, $ROLLBACK_DML);
my $outfile = '/dev/null';
my (%do_dbs,%do_tbs);

# tbname=>tbcol, tbcol: @n=>colname,type
my %tbcol_pos;

my $SPLITER_COL = ',';
my $SQLTYPE_IST = 'INSERT';
my $SQLTYPE_UPD = 'UPDATE';
my $SQLTYPE_DEL = 'DELETE';
my $SQLAREA_WHERE = 'WHERE';
my $SQLAREA_SET = 'SET';

my $PRE_FUNCT = '========================== ';

# =========================================================
# 基於row模式的binlog,生成DML(insert/update/delete)的rollback語句
# 通過mysqlbinlog -v 解析binlog生成可讀的sql文件
# 提取需要處理的有效sql
#    "### "開頭的行.如果輸入的start-position位於某個event group中間,則會導致"無法識別event"錯誤
#
# 將INSERT/UPDATE/DELETE 的sql反轉,並且1個完整sql只能佔1行
#    INSERT: INSERT INTO => DELETE FROM, SET => WHERE
#    UPDATE: WHERE => SET, SET => WHERE
#    DELETE: DELETE FROM => INSERT INTO, WHERE => SET
# 用列名替換位置@{1,2,3}
#    通過desc table獲得列順序及對應的列名
#    特殊列類型value做特別處理
# 逆序
# 
# 注意:
#    表結構與現在的表結構必須相同[謹記]
#    由於row模式是冪等的,並且恢復是一次性,所以只提取sql,不提取BEGIN/COMMIT
#    只能對INSERT/UPDATE/DELETE進行處理
# ========================================================
sub main{

    # get input option
    &get_options();

    # 
    &init_tbcol();

    #
    &do_binlog_rollback();
}

&main();


# ----------------------------------------------------------------------------------------
# Func : get options and set option flag 
# ----------------------------------------------------------------------------------------
sub get_options{
    #Get options info
    GetOptions(\%opt,
        'help',                    # OUT : print help info  
        'f|srcfile=s',            # IN  : binlog file
        'o|outfile=s',            # out : output sql file
        'h|host=s',                # IN  :  host
        'u|user=s',            # IN  :  user
        'p|password=s',        # IN  :  password
        'P|port=i',                # IN  :  port
        'start-datetime=s',        # IN  :  start datetime
        'stop-datetime=s',        # IN  :  stop datetime
        'start-position=i',        # IN  :  start position
        'stop-position=i',        # IN  :  stop position
        'd|database=s',            # IN  :  database, split comma
        'T|table=s',            # IN  :  table, split comma
        'i|ignore',                # IN  :  ignore binlog check ddl and so on
        'debug',                # IN  :  print debug information
      ) or print_usage();

    if (!scalar(%opt)) {
        &print_usage();
    }

    # Handle for options
    if ($opt{'f'}){
        $srcfile = $opt{'f'};
    }else{
        &merror("please input binlog file");
    }

    $opt{'h'} and $host = $opt{'h'};
    $opt{'u'} and $user = $opt{'u'};
    $opt{'p'} and $pwd = $opt{'p'};
    $opt{'P'} and $port = $opt{'P'};
    if ($opt{'o'}) {
        $outfile = $opt{'o'};
        # 清空 outfile
        `echo '' > $outfile`;
    }

    # 
    $MYSQL = qq{mysql -h$host -u$user -p'$pwd' -P$port};
    &mdebug("get_options::MYSQL\n\t$MYSQL");

    # 提取binlog,不需要顯示列定義信息,用-v,而不用-vv
    $MYSQLBINLOG = qq{mysqlbinlog -v};
    $MYSQLBINLOG .= " --start-position=".$opt{'start-position'} if $opt{'start-position'};
    $MYSQLBINLOG .= " --stop-position=".$opt{'stop-position'} if $opt{'stop-postion'};
    $MYSQLBINLOG .= " --start-datetime='".$opt{'start-datetime'}."'" if $opt{'start-datetime'};
    $MYSQLBINLOG .= " --stop-datetime='$opt{'stop-datetime'}'" if $opt{'stop-datetime'};
    $MYSQLBINLOG .= " $srcfile";
    &mdebug("get_options::MYSQLBINLOG\n\t$MYSQLBINLOG");

    # 檢查binlog中是否含有 ddl sql: CREATE|ALTER|DROP|RENAME
    &check_binlog() unless ($opt{'i'});

    # 不使用mysqlbinlog過濾,USE dbname;方式可能會漏掉某些sql,所以不在mysqlbinlog過濾
    # 指定數據庫
    if ($opt{'d'}){
        my @dbs = split(/,/,$opt{'d'});
        foreach my $db (@dbs){
            $do_dbs{$db}=1;
        }
    }

    # 指定表
    if ($opt{'T'}){
        my @tbs = split(/,/,$opt{'T'});
        foreach my $tb (@tbs){
            $do_tbs{$tb}=1;
        }
    }

    # 提取有效DML SQL
    $ROLLBACK_DML = $MYSQLBINLOG." | grep '^### '";
    # 去掉註釋: '### ' -> ''
    # 刪除首尾空格
    $ROLLBACK_DML .= " | sed 's/###\\s*//g;s/\\s*\$//g'";
    &mdebug("rollback dml\n\t$ROLLBACK_DML");
    
    # 檢查內容是否爲空
    my $cmd = "$ROLLBACK_DML | wc -l";
    &mdebug("check contain dml sql\n\t$cmd");
    my $size = `$cmd`;
    chomp($size);
    unless ($size >0){
        &merror("binlog DML is empty:$ROLLBACK_DML");
    };

}   


# ----------------------------------------------------------------------------------------
# Func :  check binlog contain DDL
# ----------------------------------------------------------------------------------------
sub check_binlog{
    &mdebug("$PRE_FUNCT check_binlog");
    my $cmd = "$MYSQLBINLOG ";
    $cmd .= " | grep -E -i '^(CREATE|ALTER|DROP|RENAME)' ";
    &mdebug("check binlog has DDL cmd\n\t$cmd");
    my $ddlcnt = `$cmd`;
    chomp($ddlcnt);

    my $ddlnum = `$cmd | wc -l`;
    chomp($ddlnum);
    my $res = 0;
    if ($ddlnum>0){
        # 在ddl sql前面加上前綴<DDL>
        $ddlcnt = `echo '$ddlcnt' | sed 's/^/<DDL>/g'`;
        &merror("binlog contain $ddlnum DDL:$MYSQLBINLOG. ddl sql:\n$ddlcnt");
    }

    return $res;
}


# ----------------------------------------------------------------------------------------
# Func : init all table column order
#        if input --database --table params, only get set table column order
# ----------------------------------------------------------------------------------------
sub init_tbcol{
    &mdebug("$PRE_FUNCT init_tbcol");
    # 提取DML語句
    my $cmd .= "$ROLLBACK_DML | grep -E '^(INSERT|UPDATE|DELETE)'";
    # 提取表名,並去重
    #$cmd .= " | awk '{if (\$1 ~ \"^UPDATE\") {print \$2}else {print \$3}}' | uniq ";
    $cmd .= " | awk '{if (\$1 ~ \"^UPDATE\") {print \$2}else {print \$3}}' | sort | uniq ";
    &mdebug("get table name cmd\n\t$cmd");
    open ALLTABLE, "$cmd | " or die "can't open file:$cmd\n";

    while (my $tbname = <ALLTABLE>){
        chomp($tbname);
        #if (exists $tbcol_pos{$tbname}){
        #    next;
        #}
        &init_one_tbcol($tbname) unless (&ignore_tb($tbname));
        
    }
    close ALLTABLE or die "can't close file:$cmd\n";

    # init tb col
    foreach my $tb (keys %tbcol_pos){
        &mdebug("tbname->$tb");
        my %colpos = %{$tbcol_pos{$tb}};
        foreach my $pos (keys %colpos){
            my $col = $colpos{$pos};
            my ($cname,$ctype) = split(/$SPLITER_COL/, $col);
            &mdebug("\tpos->$pos,cname->$cname,ctype->$ctype");
        }
    }
};


# ----------------------------------------------------------------------------------------
# Func : init one table column order
# ----------------------------------------------------------------------------------------
sub init_one_tbcol{
    my $tbname = shift;
    &mdebug("$PRE_FUNCT init_one_tbcol");
    # 獲取表結構及列順序
    my $cmd = $MYSQL." --skip-column-names --silent -e 'desc $tbname'";
    # 提取列名,並拼接
    $cmd .= " | awk -F\'\\t\' \'{print NR\"$SPLITER_COL`\"\$1\"`$SPLITER_COL\"\$2}'";
    &mdebug("get table column infor cmd\n\t$cmd");
    open TBCOL,"$cmd | " or die "can't open desc $tbname;";

    my %colpos;
    while (my $line = <TBCOL>){
        chomp($line);
        my ($pos,$col,$coltype) = split(/$SPLITER_COL/,$line);
        &mdebug("linesss=$line\n\t\tpos=$pos\n\t\tcol=$col\n\t\ttype=$coltype");
        $colpos{$pos} = $col.$SPLITER_COL.$coltype;
    }
    close TBCOL or die "can't colse desc $tbname";

    $tbcol_pos{$tbname} = \%colpos;
}


# ----------------------------------------------------------------------------------------
# Func :  rollback sql:    INSERT/UPDATE/DELETE
# ----------------------------------------------------------------------------------------
sub do_binlog_rollback{
    my $binlogfile = "$ROLLBACK_DML ";
    &mdebug("$PRE_FUNCT do_binlog_rollback");

    # INSERT|UPDATE|DELETE
    my $sqltype;
    # WHERE|SET
    my $sqlarea;
    
    my ($tbname, $sqlstr) = ('', '');
    my ($notignore, $isareabegin) = (0,0);

    # output sql file
    open SQLFILE, ">> $outfile" or die "Can't open sql file:$outfile";

    # binlog file
    open BINLOG, "$binlogfile |" or die "Can't open file: $binlogfile";
    while (my $line = <BINLOG>){
        chomp($line);
        if ($line =~ /^(INSERT|UPDATE|DELETE)/){
            # export sql
            if ($sqlstr ne ''){
                $sqlstr .= ";\n";
                print SQLFILE $sqlstr;
                &mdebug("export sql\n\t".$sqlstr);
                $sqlstr = '';
            }

            if ($line =~ /^INSERT/){
                $sqltype = $SQLTYPE_IST;
                $tbname = `echo '$line' | awk '{print \$3}'`;
                chomp($tbname);
                $sqlstr = qq{DELETE FROM $tbname};
            }elsif ($line =~ /^UPDATE/){
                $sqltype = $SQLTYPE_UPD;
                $tbname = `echo '$line' | awk '{print \$2}'`;
                chomp($tbname);
                $sqlstr = qq{UPDATE $tbname};
            }elsif ($line =~ /^DELETE/){
                $sqltype = $SQLTYPE_DEL;    
                $tbname = `echo '$line' | awk '{print \$3}'`;
                chomp($tbname);
                $sqlstr = qq{INSERT INTO $tbname};
            }

            # check ignore table
            if(&ignore_tb($tbname)){
                $notignore = 0;
                &mdebug("<BINLOG>#IGNORE#:line:".$line);
                $sqlstr = '';
            }else{
                $notignore = 1;
                &mdebug("<BINLOG>#DO#:line:".$line);
            }
        }else {
            if($notignore){
                &merror("can't get tbname") unless (defined($tbname));
                if ($line =~ /^WHERE/){
                    $sqlarea = $SQLAREA_WHERE;
                    $sqlstr .= qq{ SET};
                    $isareabegin = 1;
                }elsif ($line =~ /^SET/){
                    $sqlarea = $SQLAREA_SET;
                    $sqlstr .= qq{ WHERE};
                    $isareabegin = 1;
                }elsif ($line =~ /^\@/){
                    $sqlstr .= &deal_col_value($tbname, $sqltype, $sqlarea, $isareabegin, $line);
                    $isareabegin = 0;
                }else{
                    &mdebug("::unknown sql:".$line);
                }
            }
        }
    }
    # export last sql
    if ($sqlstr ne ''){
        $sqlstr .= ";\n";
        print SQLFILE $sqlstr;
        &mdebug("export sql\n\t".$sqlstr);
    }
    
    close BINLOG or die "Can't close binlog file: $binlogfile";

    close SQLFILE or die "Can't close out sql file: $outfile";

    # 逆序
    # 1!G: 只有第一行不執行G, 將hold space中的內容append回到pattern space
    # h: 將pattern space 拷貝到hold space
    # $!d: 除最後一行都刪除
    my $invert = "sed -i '1!G;h;\$!d' $outfile";
    my $res = `$invert`;
    &mdebug("inverter order sqlfile :$invert");
}

# ----------------------------------------------------------------------------------------
# Func :  transfer column pos to name
#    deal column value
#
#  &deal_col_value($tbname, $sqltype, $sqlarea, $isareabegin, $line);
# ----------------------------------------------------------------------------------------
sub deal_col_value($$$$$){
    my ($tbname, $sqltype, $sqlarea, $isareabegin, $line) = @_;
    &mdebug("$PRE_FUNCT deal_col_value");
    &mdebug("input:tbname->$tbname,type->$sqltype,area->$sqlarea,areabegin->$isareabegin,line->$line");
    my @vals = split(/=/, $line);
    my $pos = substr($vals[0],1);
    my $valstartpos = length($pos)+2;
    my $val = substr($line,$valstartpos);
    my %tbcol = %{$tbcol_pos{$tbname}};
    my ($cname,$ctype) = split(/$SPLITER_COL/,$tbcol{$pos});
    &merror("can't get $tbname column $cname type") unless (defined($cname) || defined($ctype));
    &mdebug("column infor:cname->$cname,type->$ctype");

    # join str
    my $joinstr;
    if ($isareabegin){
        $joinstr = ' ';
    }else{
        # WHERE 被替換爲 SET, 使用 ,  連接
        if ($sqlarea eq $SQLAREA_WHERE){
            $joinstr = ', ';
        # SET 被替換爲 WHERE 使用 AND 連接
        }elsif ($sqlarea eq $SQLAREA_SET){
            $joinstr = ' AND ';
        }else{
            &merror("!!!!!!The scripts error");
        }
    }
    
    # 
    my $newline = $joinstr;

    # NULL value
    if (($val eq 'NULL') && ($sqlarea eq $SQLAREA_SET)){
        $newline .= qq{ $cname IS NULL};
    }else{
        # timestamp: record seconds
        if ($ctype eq 'timestamp'){
            $newline .= qq{$cname=from_unixtime($val)};
        # datetime: @n=yyyy-mm-dd hh::ii::ss
        }elsif ($ctype eq 'datetime'){
            $newline .= qq{$cname='$val'};
        }else{
            $newline .= qq{$cname=$val};
        }
    }
    &mdebug("\told>$line\n\tnew>$newline");
    
    return $newline;
}

# ----------------------------------------------------------------------------------------
# Func :  check is ignore table
# params: IN table full name #  format:`dbname`.`tbname`
# RETURN:
#        0 not ignore
#        1 ignore
# ----------------------------------------------------------------------------------------
sub ignore_tb($){
    my $fullname = shift;
    # 刪除`
    $fullname =~ s/`//g;
    my ($dbname,$tbname) = split(/\./,$fullname);
    my $res = 0;
    
    # 指定了數據庫
    if ($opt{'d'}){
        # 與指定庫相同
        if ($do_dbs{$dbname}){
            # 指定表
            if ($opt{'T'}){
                # 與指定表不同
                unless ($do_tbs{$tbname}){
                    $res = 1;
                }
            }
        # 與指定庫不同
        }else{
            $res = 1;
        }
    }
    #&mdebug("Table check ignore:$fullname->$res");
    return $res;
}


# ----------------------------------------------------------------------------------------
# Func :  print debug msg
# ----------------------------------------------------------------------------------------
sub mdebug{
    my (@msg) = @_;
    print "<DEBUG>@msg\n" if ($opt{'debug'});
}


# ----------------------------------------------------------------------------------------
# Func :  print error msg and exit
# ----------------------------------------------------------------------------------------
sub merror{
    my (@msg) = @_;
    print "<Error>:@msg\n";
    &print_usage();
    exit(1);
}

# ----------------------------------------------------------------------------------------
# Func :  print usage
# ----------------------------------------------------------------------------------------
sub print_usage{
    print <<EOF;
==========================================================================================
Command line options :
    --help                # OUT : print help info  
    -f, --srcfile            # IN  : binlog file. [required]
    -o, --outfile            # OUT : output sql file. [required]
    -h, --host            # IN  : host. default '127.0.0.1'
    -u, --user            # IN  : user. [required]
    -p, --password            # IN  : password. [required] 
    -P, --port            # IN  : port. default '3306'
    --start-datetime        # IN  : start datetime
    --stop-datetime            # IN  : stop datetime
    --start-position        # IN  : start position
    --stop-position            # IN  : stop position
    -d, --database            # IN  : database, split comma
    -T, --table            # IN  : table, split comma. [required] set -d
    -i, --ignore            # IN  : ignore binlog check contain DDL(CREATE|ALTER|DROP|RENAME)
    --debug                # IN  :  print debug information

Sample :
  shell> perl binlog-rollback.pl -f 'mysql-bin.000001' -o '/tmp/t.sql' -u 'user' -p 'pwd' 
  shell> perl binlog-rollback.pl -f 'mysql-bin.000001' -o '/tmp/t.sql' -u 'user' -p 'pwd' -i
  shell> perl binlog-rollback.pl -f 'mysql-bin.000001' -o '/tmp/t.sql' -u 'user' -p 'pwd' --debug
  shell> perl binlog-rollback.pl -f 'mysql-bin.000001' -o '/tmp/t.sql' -h '192.168.1.2' -u 'user' -p 'pwd' -P 3307
  shell> perl binlog-rollback.pl -f 'mysql-bin.000001' -o '/tmp/t.sql' -u 'user' -p 'pwd' --start-position=107
  shell> perl binlog-rollback.pl -f 'mysql-bin.000001' -o '/tmp/t.sql' -u 'user' -p 'pwd' --start-position=107 --stop-position=10000
  shell> perl binlog-rollback.pl -f 'mysql-bin.000001' -o '/tmp/t.sql' -u 'user' -p 'pwd' -d 'db1,db2'
  shell> perl binlog-rollback.pl -f 'mysql-bin.0000*' -o '/tmp/t.sql' -u 'user' -p 'pwd' -d 'db1,db2' -T 'tb1,tb2'
==========================================================================================
EOF
    exit;  
}


1;

  


腳本謹慎使用。



例子:


perl binlog-rollback.pl -f 'mysql-bin.000001' -o '/tmp/t.sql' -h '192.168.1.2' -u 'user' -p 'pwd' -P 3307


提供用戶名和密碼是爲了確定表結構,其他用法可參考案例。


得到的結果爲事物的反向語句。delete--->insert,insert--->delete 如下:

INSERT INTO cqdata.cq_herodata SET `id`=23, `serverId`=1, `userId`=2, `tempId`=2, `starArr`='way', `gemArr`='wa', `wingArr`='way', `equipData`='way', `sex`=2, `combat`=4327896, `recordTime`='2016-06-06 15:37:03';
INSERT INTO cqdata.cq_herodata SET `id`=22, `serverId`=1, `userId`=2, `tempId`=2, `starArr`='way', `gemArr`='wa', `wingArr`='way', `equipData`='way', `sex`=2, `combat`=4327896, `recordTime`='2016-06-06 15:37:03';
INSERT INTO cqdata.cq_herodata SET `id`=21, `serverId`=1, `userId`=2, `tempId`=2, `starArr`='way', `gemArr`='wa', `wingArr`='way', `equipData`='way', `sex`=2, `combat`=4327896, `recordTime`='2016-06-06 15:37:02';
INSERT INTO cqdata.cq_herodata SET `id`=20, `serverId`=1, `userId`=2, `tempId`=2, `starArr`='way', `gemArr`='wa', `wingArr`='way', `equipData`='way', `sex`=2, `combat`=4327896, `recordTime`='2016-06-06 15:37:02';
INSERT INTO cqdata.cq_herodata SET `id`=19, `serverId`=1, `userId`=2, `tempId`=2, `starArr`='way', `gemArr`='wa', `wingArr`='way', `equipData`='way', `sex`=2, `combat`=4327896, `recordTime`='2016-06-06 15:37:01';
INSERT INTO cqdata.cq_herodata SET `id`=18, `serverId`=1, `userId`=2, `tempId`=2, `starArr`='way', `gemArr`='wa', `wingArr`='way', `equipData`='way', `sex`=2, `combat`=4327896, `recordTime`='2016-06-06 15:37:01';
INSERT INTO cqdata.cq_herodata SET `id`=17, `serverId`=1, `userId`=2, `tempId`=2, `starArr`='way', `gemArr`='wa', `wingArr`='way', `equipData`='way', `sex`=2, `combat`=4327896, `recordTime`='2016-06-06 15:37:00';
INSERT INTO cqdata.cq_herodata SET `id`=16, `serverId`=1, `userId`=2, `tempId`=2, `starArr`='way', `gemArr`='wa', `wingArr`='way', `equipData`='way', `sex`=2, `combat`=4327896, `recordTime`='2016-06-06 15:37:00';
DELETE FROM cqdata.cq_herodata WHERE `id`=23 AND `serverId`=1 AND `userId`=2 AND `tempId`=2 AND `starArr`='way' AND `gemArr`='wa' AND `wingArr`='way' AND `equipData`='way' AND `sex`=2 AND `combat`=4327896 AND `recordTime`='2016-06-06 15:37:03';
DELETE FROM cqdata.cq_herodata WHERE `id`=22 AND `serverId`=1 AND `userId`=2 AND `tempId`=2 AND `starArr`='way' AND `gemArr`='wa' AND `wingArr`='way' AND `equipData`='way' AND `sex`=2 AND `combat`=4327896 AND `recordTime`='2016-06-06 15:37:03';
DELETE FROM cqdata.cq_herodata WHERE `id`=21 AND `serverId`=1 AND `userId`=2 AND `tempId`=2 AND `starArr`='way' AND `gemArr`='wa' AND `wingArr`='way' AND `equipData`='way' AND `sex`=2 AND `combat`=4327896 AND `recordTime`='2016-06-06 15:37:02';


`insert into XXX` 實際上爲刪除了該條記錄。`delete from  XXX ` 實際爲插入了這條語句。對文件按照庫名錶名進行處理。導入mysql即可按照要求恢復數據。





 















發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章