MySQL道普請便り

第16回MySQLのエラーコードについて

第12回第14回とMySQLのヘルスチェックについて紹介しました。MySQLの運用にはヘルスチェックも欠かせませんが、クライアント(アプリケーション)サイドでどのようなエラーを受け取ったかを記録、把握することも大切です。クライアントサイドで受け取ったエラーを監視することはつまり、実際に起こったエラーを監視することであり、究極の死活監視でもあります(アプリケーションサイドに起因するエラーも拾ってしまうため、やはりこれも銀の弾丸ではありませんが⁠⁠。

今回は、クライアントがMySQLからエラーを受け取る際の動作について説明します。

エラーに関するクライアント間の差異

MySQLから見た場合、クライアントにどのようなライブラリが利用されていても応答に変わりはありません。しかし、クライアントライブラリ側(あるいは、それよりも上のフレームワークのレイヤー)でMySQLから受け取ったエラーを変換することは往々にしてあります。

MySQLのエラーコードは4桁の数字ですが、それぞれ意味がわかりやすいように短い英文になったマクロが定義されています。エラーコード1040番にはER_CON_COUNT_ERRORというマクロが定義されており、対応する(英語の)エラーメッセージは"Too many connections"、という具合です。これはMySQLに標準でバンドルされているCクライアントライブラリConnecotr/C共有ライブラリの名前でいうとlibmysqlclient.soがこれにあたります)ではサーバ、クライアント間の互換があります。

JavaのクライアントライブラリであるConnector/Jでは、ER_CON_COUNT_ERRORSQL_STATE_CONNECTION_REJECTEDマッピングされています。Connector/Cはエラーコードベースでエラーをハンドリングしますが、Connector/JはSQLSTATEベースでエラーをハンドルするようです。これはエラーコードがMySQL特有のものであることに対し、SQLSTATEはSQL標準で定義されており、JDBCもSQLSTATEに準拠しているため、それに合わせた実装になっているのでしょう。そしてSQL_STATE_CONNECTION_REJECTEDには"Data source rejected establishment of connection"というエラーメッセージが紐づけられています

このように、クライアントライブラリが違うことでエラーメッセージが違ったものになることはあり得ることです。多くの場合問題になることはないと思いますが、あるクライアントライブラリから別のクライアントライブラリに移行する場合や、クライアント側のエラーメッセージからMySQLサーバ側の原因を特定しなければならない時には、このようなことが起こり得ることを憶えておいてください。

クライアントがMySQLから受け取る情報

リファレンスマニュアルのサーバのエラーコードおよびメッセージにも記載がありますが、より詳しくはMySQLプロトコルのマニュアルに記載があります。MySQLサーバから返却するエラーレスポンスにはエラーコードとSQLSTATE、エラーメッセージが含まれています。

エラーコードは(2015/03現在のところ)4桁の整数で、1000番台と3000番台のものがあります。MySQL 5.6とそれ以前のバージョンでは、サーバサイドは1000番台のエラーのみでしたが、エラーコードの増加に伴いMySQL 5.7から3000番台が追加されています。2000番台はクライアント側のエラーコードとして予約されています。Connector/Cでは2000番台のエラーコードも積極的に利用しますが、それ以外のクライアントライブラリではどのように扱っているかは筆者は知りません。

$ mysql -h127.0.0.1 -P64058 -uroot -p
Enter password:
ERROR 2003 (HY000): Can't connect to MySQL server on '127.0.0.1' (111)

$ mysql -h127.0.0.1 -P64057 -uroot -p
Enter password:
ERROR 1130 (HY000): Host '127.0.0.1' is not allowed to connect to this MySQL server

$ mysql -hlocalhost -S/usr/mysql/5.7.11/data/mysql.sock -uroot -p
Enter password:
ERROR 1045 (28000): Access denied for user 'root'@'localhost' (using password: YES)

最初のコマンドは、ポート番号を間違えて接続しようとした際のエラーです。⁠クライアントがMySQLサーバに到達できなかった」ため、クライアントサイドのエラー番号である2003番が返却されています。クライアントサイドなのでSQLSTATEは存在せず、汎用のSQLSTATEであるHY000が入っています。続く文字列はエラーメッセージですが、これもMySQLサーバから返却されたものではなく、クライアント側で生成したものです。2番目のコマンドは「MySQLサーバに到達できたが、認証のホストチェックに失敗した」場合のエラーです。MySQLサーバが能動的に認証を拒否しているため、サーバサイドのエラー番号である1130番が返却されています。3番目も同様に、⁠MySQLサーバに到達しホストチェックも成功したが、パスワードが違っていた」エラーです。エラー番号は1045番、SQLSTATEには28000が入りました。

このように、mysqlコマンドラインクライアントからMySQLサーバに接続できない」という単一の事象であっても、原因となった箇所によりエラーコードやエラーメッセージは異なってきます。経験を積むと、このあたりの相違だけである程度エラーか所の原因が切り分けられるようになってきます。コツというほどでもありませんが、人間が事象を判断する時はエラー番号ではなくエラーメッセージ、アプリケーションの中で機械的に判断させる時はエラー番号(Connector/JであればSQLSTATE)で判断することがトラブルシュートの上達を助けてくれると思います。

全くの余談ですが、MySQLサーバ側から返却されるエラーメッセージの値は日本語にすることもできます。/etc/my.cnfなどの設定ファイルにlc_messages=ja_JPと追加することで以下の例のように日本語にすることはできますが、そもそもエラーメッセージは単純な英語であるため、わざわざ日本語で出力させなくても大概のものは理解可能ですし、日本語のエラーメッセージで検索しようとした場合に検索しにくいこと(日本の大半のMySQLはデフォルトのen_USのままでエラーメッセージを英語で出力するため、Webにある情報も英語のものがほとんどです)が挙げられます。

$ mysql -hlocalhost -S/usr/mysql/5.7.11/data/mysql.sock -uroot -p
Enter password:
ERROR 1045 (28000): ユーザー 'root'@'localhost' のアクセスは拒否されました。(using password: YES)

MySQLからのエラーをモニタリングする

最初の段落でも少し触れましたが、アプリケーションからのエラーメッセージは優れたヘルスチェックのひとつです。ヘルスチェック用のスクリプトはあくまでそのスクリプトを設計した範囲でMySQLの状態を確認できますが、アプリケーションのエラーメッセージはアプリケーションが実際に失敗した動作を知ることができます。

これはヘルスチェック用のスクリプトと比べて、次のような特徴があります。

1.影響の大小を判定しやすい

たとえば、ある特定のレコードが長期間ロックされたままになっている事態を想定します。その特定のレコードがアプリケーションの動作に不可欠なレコードだった場合、たった1行の長期間のロックはアプリケーションに多大な影響を与えます(そして、大量のエラーがアプリケーションに返るでしょう⁠⁠。逆に、1ヵ月に1回しか必要とされないレコードであれば、実際に利用するタイミングまではロックが続いていてもそれほど問題になることはないでしょう(エラーログは出ても散発的になります⁠⁠。これらはMySQL(および、MySQLを全体的に監視するスクリプト)からすれば、飽くまで「あるテーブルのある行が長時間ロックされていた」というだけであり、それがどの程度の影響かを測るのは困難です。

2.どこに問題があるかの絞り込みまでは期待できない

エラー番号2006番のCR_SERVER_GONE_ERRORは、コネクションがMySQLサーバから一方的に切断された場合にクライアントライブラリ(Connector/Cの場合。他のクライアントライブラリの場合は異なります)が出力するエラーコードです(エラー番号の2000番台はクライアント用に予約されていますので、MySQLサーバから2000番台のエラーが返却されることはありません⁠⁠。アプリケーションから見れば、それがサーバでエラーになったのかクライアントライブラリの中でエラーになったのかは特に判定されず、⁠クエリーを実行しようとしたところエラーになった」という情報が渡されます。サーバ側のエラーなのかクライアント側のエラーなのかは、例外機構の中でエラーコードを見て判定することになるでしょう(あるいは、それは判別する必要はないかも知れません⁠⁠。

このエラーコードの原因になり得る原因をMySQLサーバ側から探すと、このエラーをクライアントライブラリが発し得るのは、KILLステートメントを利用して人為的にコネクションを切断した」場合や「アイドルタイムアウトwait_timeoutまたはinteractive_timeoutによりコネクションが切断された」場合、⁠MySQLサーバがダウンし、コネクションが破棄されてしまった」場合などが考えられます。またサーバ側にもクライアント側にも問題がなかったとしても、中間のネットワークやNICの状態の変化、ファイアウォールの設定変更など、原因になり得る事象はいくつも存在します。これらはエラーメッセージからだけでは原因は追究できません。

3.記録として残しやすい

MySQLサーバにとってはエラーの返却は「エラーになるべき処理を中断し、エラーコードを返却した」という正常な動作です(たとえば、存在しないテーブルに対するINSERTステートメントがそのようにエラー処理されないのであれば、それこそがMySQLサーバにとっての異常(=バグ)です⁠⁠。基本的にMySQLのエラーログにはクライアントに返却したエラーコードやその詳細は記録されませんので、クライアント側でログを残すことになります。昨今、アプリケーションの動作ログはユーザーの行動情報などを抽出するために保管される機会が増えていますので、MySQLのエラーも併せて(同じログファイルである必要はありませんが)記録しておくことで、MySQLで問題が発生したタイミングとユーザー動向の相関や周期性などを発見しやすくすることができます。

サーバサイドの死活監視とクライアントサイドでのエラーハンドリングを併せて上手く監視することで、より効率的なMySQLのヘルスチェックをしてみてください。

おすすめ記事

記事・ニュース一覧