ちょっと硬派なコンピュータフリークのBlogです。

カスタム検索

2009-12-02

特定のデータベースだけをmysqldumpで作成したダンプファイルから抜き出すawkスクリプト

タイトルのまんまのプチトリビアを紹介しようと思う。mysqlの--one-databaseオプションを使えば「mysqldumpで--all-databasesとか--databasesオプションを使って作成したダンプファイルに含まれる複数のデータベースから、一つのデータベースだけを選択してリストアする」という操作ができるけど、毎回ダンプファイル全体を読み込むのは無駄じゃないか?と思われることもあるだろう。だったら事前にダンプファイルを分けちゃいたい!と考えるのが人情というもの。そんなときはawkコマンドを使うといい。
#!/usr/bin/awk -f

BEGIN {
  dump_current_db = 0;
  num_db = split(databases, db_arr, ",")
  for (i = 1; i <= num_db; i++) {
    db_arr[i] = "`" db_arr[i] "`";
  }
}

/^\-\-/ {
  print $0;
  if ($2 == "Current" && $3 == "Database:") {
    dump_current_db = 0;
    for (i = 1; i <= num_db; i++) {
      if ($4 == db_arr[i]) {
        dump_current_db = 1;
        break;
      }
    }
  }
}

/^\/\*.+\*\/;/ { print $0 }

/^$/ { print $0 }

/^[^\-]/ { if (dump_current_db == 1) print $0 }
このawkスクリプトを例えばcut-database.awkとかに保存して、
shell> ./cut-database -v databases=db1,db2,db3... < dump.sql > sub_dump.sql
てな具合にコマンドを起動すれば、目的のデータベースだけを含んだダンプファイルがいっちょあがり。 そもそもmysqldumpコマンドでダンプするときにファイルを分ければ?なんて考える人も居るかも知れないけど、それは出来ない相談だったりする。ロールフォワードリカバリとかを考えてバイナリログと同期をとっておかないといけなかったりするし、そもそもデータベースが複数あるときにはデータベース間でデータが同期していないといけない。ところがデータベースを個別にダンプするとロールフォワードリカバリが出来ないどころか、データが使い物にならないことになってしまう。よって--all-databasesとか--databasesの利用は必須なのである。 そこでもう一つ紹介するのが、mysqldumpコマンドの出力をそのまま別々のファイルに保存するawkスクリプト。
#!/usr/bin/awk -f

function is_new_db(db_name) {
  for (i = 1; i <= num_db; i++) {
    if (db_name == db[i]) {
      return 0;
    }
  }
  return 1;
}

BEGIN {
  num_db = 0
  num_prelines = 0
  num_postlines = 0
  current_file = "/dev/null"
}

/^\-\-/ {
  if ($2 == "Current" && $3 == "Database:") {
    close(current_file);
    db_name = $4
    gsub("`", "", db_name);
    current_file = db_name ".sql";
    if (is_new_db(db_name)) {
      db[++num_db] = db_name;
      print "--\n" $0 "\n--\n" > current_file;
      for (i = 1; i <= num_prelines; i++)
        print prelines[i] >> current_file;
    }
  } else if (num_db == 0) {
    num_prelines++;
    prelines[num_prelines] = $0;
  } else if ($2 == "Dump" && $3 == "completed") {
    num_postlines++;
    postlines[num_postlines] = "";
    num_postlines++;
    postlines[num_postlines] = $0;
  } else {
    print $0 >> current_file
  }
  next;
}

/^\/\*.+\*\/;/ {
  if (match($0, "character|collation")) {
    print $0 >> current_file;
  } else if (match($0, "SET")) {
    if (num_db == 0) {
      if (match(prelines[num_prelines], "^\-\-")) {
        num_prelines++;
        prelines[num_prelines] = "";
      }
      num_prelines++;
      prelines[num_prelines] = $0;
    } else {
      num_postlines++;
      postlines[num_postlines] = $0;
    }
  } else {
    print $0 >> current_file;
  }
  next;
}

{ print $0 >> current_file }

END {
  for (i = 1; i <= num_db; i++) {
    current_file = db[i] ".sql";
    print "" >> current_file
    for (j = 1; j <= num_postlines; j++) {
      print postlines[j] >> current_file;
    }
  }
}
このスクリプトを例えばseparate-dump.awkというファイルに保存して実行権限を与えてやれば、
shell> mysqldump -A --single-transaction --master-data=2 --flush-logs | ./separate-dump.awk
という具合にこのコマンドを使うことができる。そうするとカレントディレクトリに「データベース名.sql」というファイルにダンプが分けられて保存されるという寸法。DB数が多い場合は"Too many open files”エラーが出てしまうので注意すること。(色々直しました。) もちろん既存のダンプファイルを分けることも可能。
shell> ./separate-dump.awk < dump.sql

なお、このスクリプトの利用は自己責任でお願いします。ライセンスはGPLv3で。何か不具合等があれば直して使って下さい。

1 コメント:

ウッソ★ さんのコメント...

「cut-database.awk」便利です、ありがとうございます。

コメントを投稿