訳者注 2014.12.05 この機能はMySQL 5.5.41、5.6.22で使用できないように修正された。詳細は本文の最後を参照
もう4年半ぐらい前、コードのリファクタリングをしている最中に、sql_log_bin
は、SESSION変数としてだけではなく、GLOBAL変数としても使えるように決定されてしまった(あるいは間違って変更された?)。2年ほど前、TwitterでMySQL 5.5のアップグレードを実施していた時、その変更がいかに愚かで、いかに危険であるかを詳しく書いてMySQLバグ67433として登録し、その変更の切り戻しを依頼した。
それから、何の変更も切り戻しも行われず、SET GLOBAL sql_log_bin
は実行可能なままだ。そして、間違ってSET GLOBAL sql_log_bin
を設定したことによる被害を、直接見たり、聞いたりし続けてきた。そんなわけで、ここではそれがいかに愚かで危険かをお見せしよう。
SET GLOBAL sql_log_binを実行すると何が起こる?
マスタスレーブのレプリケーション構成をとっている場合、バイナリロギングが有効になっていて、各トランザクションはバイナリログに記録され、スレーブはレプリケーションのためにそのバイナリログを(おおよそリアルタイムで)読むだろう。このバイナリログは、レプリケーションが有意義で信頼できるものであるためには完全でなければならない。これが不完全だと、深刻なデータロストが発生することになる。
セッションで実行したことをバイナリログへ一時的に書き込みしないようにするために、SET [SESSION] sql_log_bin=0
を使うことは昔から可能だったことだ。これは、例えばALTER TABLEをスレーブへすぐにはレプリケーションせずに実行したいといった時に結構便利な機能だ。
多くの人は、SET GLOBAL sql_log_bin=0
を、全てのセッション(GLOBALの意味分かってるかな?)でバイナリロギングを無効にするような機能だと思っているのではないか?しかし、これは全くもって事実と異なる。
背景として、MySQLサーバの変数には、以下のような3つの異なるスコープが存在する。
- グローバルのみ - グローバルなスコープでのみ存在して、意味をなす変数。例えば、innodb_buffer_pool_sizeがこれにあたる。バッファープールは全体で1つしか存在せず、セッションごとに何かあるわけではない。この変数は読み取り専用で、実行時には変更できない。
- グローバルとセッション - グローバル変数はセッションでのデフォルト値を定義しており、接続時にセッション変数としてコピーされる。必要なら、セッション内でユーザが変更することも可能だが、制限が存在する場合もある。グローバル変数が変更されたら、それ以降の新しいセッションには反映されるが、既存のセッションではセッション開始時の値が保持される。この典型的な例としては、max_allowed_packetがある。
- セッションのみ - セッション変数に関連づいたグローバル変数が存在しない。セッション内だけで変数を変更できる。
sql_log_bin変数は、以前はセッションのみだったのに、上で書いた変更の結果、グローバルとセッションになってしまった。
これには、以下のような意味合いがある。
- セッションが生成される時、sql_log_binの値はグローバルスコープからセッションスコープにコピーされる。
- DBAが
SET GLOBAL sql_log_bin=0
を実行すると、その変更は新しいセッションのみに反映され、既に張られているセッションはバイナリログへの記録を続ける。つまり、フェイルオーバには使い物にならなくなってしまう。 - SET GLOBALで変更したグローバル変数は、現在接続中のセッションには影響しない。つまり、
SET GLOBAL sql_log_bin=0
をあるセッション内で実行しても、その後にそのセッションで実行したコマンドをバイナリログに記録しないことにはならない。これが、DBAが期待する結果の通りにならない最初のサインになることが多い。スレーブへレプリケーションしないつもりでいたものが、全スレーブで実行されてしまっていることに、ある時気づいてしまうのだ。 - sql_log_bin=0でコネクションが作られている間に、DBAが
SET GLOBAL sql_log_bin=1
を実行すると、sql_log_bin=0で作られたセッションでは、そのセッションの接続が続いている間、バイナリログへの記録はされない。この場合も、フェイルオーバには使い物にならないサーバになってしまう。 - DBAがうっかり
SET GLOBAL sql_log_bin=0
を実行してしまうと、例え数ミリ秒で元に戻したとしても、マスタスレーブ間のレプリケーションは修復不可能なダメージを受ける。すぐに気づいて対処しないと、永続的な致命的データロストが起きるだろう。
誰かうまく使っている人なんているんだろうか?
MySQLバグ67433のOracleからの唯一のコメントで、Sveta Smirnova氏は以下のように書いている。
「切り戻し」についてなら賛成しかねる。グローバル変数にしておけば、マスタをダウンさせずに変更できるから。
Googleで"set global sql_log_bin"を検索してみると、色々な言語で、たくさんの人々がこの例について取り上げているのを発見できる。私は、この問題について正しい認識を持っている記事や人を見つけることはできなかったが、間違った例は次から次に見つけた。
ここで、不名誉殿堂入りの記事を紹介しよう。
- Akshay Suryawanshi: Beware !!! Danger ahead “sql_log_bin”… - Perconaの顧客が、間違って
SET GLOBAL sql_log_bin
をSESSIONの代わりに実行してしまった例。データは1.2TBと巨大で、3日の退屈な作業の末、スレーブは完全に同期された。 - 这几年犯的错 (“recent mistakes”) -
SET GLOBAL sql_log_bin
を本番環境で誰かが実行したため、その不整合の修復に時間を費やした例。 - Gavin Towey: How can MySQL Replication Break? - 「マスタのバイナリログがレプリケーションされるのを止めるのは難しいとお思いだろう。しかしそんなことはない。SET GLOBAL SQL_LOG_BIN=0を実行すればいい。おっと、エラーを出すサーバはないからね。」
- Easiest way to sync a dev MySQL db to a production MySQL db? - 同期されていない本番とステージング環境を扱うざっくりとした方法の説明。
- MySQL Replication FAQ - 誰かのDBA向けメモだが、間違った使い方をしている。
- 总结:MySQL备份与恢复的三种方法 - 完全に間違っており、マスタのみ構成でmysqldumpするには非常に危険な手順が書いてある。
- 标题:MySQL 参数浅析之 sql_log_bin - どのようにsql_log_binを使えばいいかの解説で、GLOBALをつけた場合の危険性について厳しい警告を発している。
- ZabbixのDB(MySQL)をパーティショニングする - Zabbixの監視スクリプトがあるが、そこに含まれている
SET GLOBAL sql_log_bin
のせいで、本番環境は完全に壊れてしまうだろう。 - 标题: MySQL备份与还原 - フォーラムで、誰かが間違ったアドバイスをしている。
- リストアの高速化 - リストアを高速化する方法で、間違ったアドバイスをしている。
合わせて書いておくと、MySQLマニュアルのsql_log_binの記述は、適切な説明が書かれておらず、振る舞いについての警告も十分でない。
Oracleよ、君たちの動き次第だ
正しいことをせよ、そしてこの変更を元に戻すべし。パッチすら書いてあげたのだから。
アップデート
OracleのMySQLのコミュニティマネージャMorgan Tocker氏が、SET GLOBAL sql_log_binの機能についてのフィードバックを求めている。確認して意見を表明しよう!
(訳者追記 2014.10.18) 原文のコメントとして以下の情報あり。
参考まで、バグ番号67433はクローズされ、SET GLOBAL SQL_LOG_BIN変数は、次の5.5、5.6、5.7のリリースでなくなる予定だ(文はエラーが出て失敗するようになる)。 しばらくの間、グローバル変数として「読む」ことはできるが、それも次のMySQLバージョンでなくなるだろう。
(訳者追記 2014.12.05) 上記の通り、5.5.41および5.6.22で改善された。
@yyamasaki1さん情報提供ありがとうございます。
以下、リリースノートの抜粋(日本語訳は訳者)。
レプリケーション: sql_log_binシステム変数のグローバルスコープは廃止され、この変数はセッションスコープでしか設定できなくなりました。SET GLOBAL SQL_LOG_BIN文はエラーを返すようになります。今のところsql_log_binのグローバル値は読み取り可能ですが、アプリケーションがこの値に依存した部分は、全て削除しておくべきです。将来のMySQLのリリースで、その機能は削除される予定です。