Yakstは、海外の役立つブログ記事などを人力で翻訳して公開するプロジェクトです。
10年以上前投稿 修正あり

愚かで、しかも危険なSET GLOBAL sql_log_bin

MySQLのレプリケーション環境を運用していると、バイナリログへの記録を一時的に止めるためにsql_log_bin=0を実行したことがあるだろう。これを「set GLOBAL sql_log_bin=0」と、グローバル変数への変更として実行すると大変なことになる、という指摘。TwitterのMySQLエンジニア、Jeremy Cole氏の提言。

原文
Stupid and dangerous: SET GLOBAL sql_log_bin – Jeremy Cole (English)
翻訳依頼者
D98ee74ffe0fafbdc83b23907dda3665
翻訳者
D98ee74ffe0fafbdc83b23907dda3665 doublemarket
原著者への翻訳報告
未報告


訳者注 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"を検索してみると、色々な言語で、たくさんの人々がこの例について取り上げているのを発見できる。私は、この問題について正しい認識を持っている記事や人を見つけることはできなかったが、間違った例は次から次に見つけた。

ここで、不名誉殿堂入りの記事を紹介しよう。

合わせて書いておくと、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のリリースで、その機能は削除される予定です。

次の記事
いつどのようにInnoDBを共通テーブルスペースから移動するか
前の記事
MySQLのデータベース監査

Feed small 記事フィード

新着記事Twitterアカウント