Quantcast
Channel: なからなLife
Viewing all articles
Browse latest Browse all 172

SELECT文をタイムアウト強制終了させる「MAX_EXECUTION_TIME」使ってる?

$
0
0

この記事はMySQL Casual Advent Calendar 2017 - Qiitaの9日目のエントリとなります。

実行が長引いたSELECT文を強制終了させるヤーツ

MySQL5.6まで、正常に処理が進んでいて遅いSELECTをタイムアウトさせるシステム変数はありませんでした。


正常に処理が進んでいない時のパラメータだと

  • lock_wait_timeout:メタデータロック取得待ち
  • innodb_lock_wait_timeout:レコードロック取得待ち

がありました。


正常に処理が進んでいるけど、厳密には「処理中」ではないときに効くパラメータだと

  • net_read_timeout:クライアントからサーバに送り込んだデータの読み込み時間
  • net_write_timeout:サーバからクライアントへのデータの書き戻し時間

がありました。


他にも、アイドルタイムアウト系で

  • interactive_timeout
  • wait_timeout

などもありました。


しかし、MySQLさん、ちょっと重いSQL投げると延々帰ってこないことがあったりするのに、それをタイムアウトさせるサーバー側の設定がなく、クライアントサイドで対応する必要がありました。

例えば、Javaプログラムの場合、JDBCドライバ「Connector/J」に対して「SocketTimeout」で「クライアントから見て、リクエストがnミリ秒帰ってこなかったら諦めるよ」って形をとる必要がありました。


使用している言語がJavaじゃなかったら、、、わかりません。ドキュメント漁ってください。

ザクッと読んだ限り、connection_timeout、つまり「新規接続時にサーバーが見つからない/返事がない」ヤツっぽいタイムアウトと、コネクションの確立後に投げたリクエストの返答までの時間が間延びしてるタイムアウトが読み分けにくい。。。

Connector/.NETの「MySqlCommand.CommandTimeout Property
なんかはイケそうな感じだし、これ以外は。。。。


シェルスクリプトmysql -e "SQL文"とかやってたら、、、mysqlプログラムのオプションにはSocketTimeOut相当のパラメータはありません。。。


しかし、MySQL 5.7以降(もちろん8.0も)、表題に上げた「MAX_EXECUTION_TIME」によって、実行自体が長引くSELECT文にミリ秒指定でタイムアウト設定をすることが可能になっています。

MAX_EXECUTION_TIMEの使い方

GLOBAL/SESSION VARIABLEで指定する

my.cnfで指定するなり、SET句でしていするなり、今まで通りのサーバーシステム変数の扱いと同じです。

これに限った話ではありませんが、SETでGLOBALだけ設定しても、現在セッションには影響ありません。
GLOBALにSETしたあとにログインするか、SESSIONレベルでSETしましょう。


以下、100万件超、普通に投げると20秒弱かかるSELECT文に対して、「MAX_EXECUTION_TIME」で1000ミリ秒を指定したケースです。

「ERROR 3024 (HY000): Query execution was interrupted, maximum statement execution time exceeded」で強制終了されますが、これが出るまでに何秒かかったかの表示はないです。。。

mysql> selectcount(*)from test_a;
+----------+
| count(*) |
+----------+
|  1048576 |
+----------+
1rowin set (19.07 sec)

mysql> set global max_execution_time=1000;
Query OK, 0rows affected (0.00 sec)

mysql> select@@global.max_execution_time,@@session.max_execution_time;
+-----------------------------+------------------------------+
| @@global.max_execution_time | @@session.max_execution_time |
+-----------------------------+------------------------------+
|                        1000 |                            0 |
+-----------------------------+------------------------------+
1rowin set (0.00 sec)

mysql> selectcount(*)from test_a;
+----------+
| count(*) |
+----------+
|  1048576 |
+----------+
1rowin set (18.97 sec)

mysql> exit
Bye
[root@mysql57 ~]# mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands endwith ; or \g.
Your MySQL connection id is5
Server version: 5.7.20 MySQL Community Server (GPL)

Copyright (c) 2000, 2017, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type'help;'or'\h'for help. Type'\c'to clear the current input statement.

mysql> select@@global.max_execution_time,@@session.max_execution_time;
+-----------------------------+------------------------------+
| @@global.max_execution_time | @@session.max_execution_time |
+-----------------------------+------------------------------+
|                        1000 |                         1000 |
+-----------------------------+------------------------------+
1rowin set (0.00 sec)

mysql> selectcount(*)from test_a;
ERROR 1046 (3D000): Nodatabase selected
mysql> use exe_test;
Reading table information for completion of tableandcolumn names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> selectcount(*)from test_a;
ERROR 3024 (HY000): Query execution was interrupted, maximum statement execution time exceeded
オプティマイザ・ヒントで指定する

オプティマイザ・ヒント自体がMySQL 5.7からの新機能で、「/*+ XXXXXXXXX */」の形式で、BKAJやMRRといったアルゴリズム採用をヒント句の形で与えられるものです。
その中で、ちょっと異色なヒントとして、MAX_EXECUTION_TIMEが指定できます。

そのSQLだけに指定できるので、セッションレベルよりも細かい制御ができて便利ですね。

気をつけたいのは、システム変数との違いです。
MAX_EXECUTION_TIMEは「=nミリ秒」ではなく、「(nミリ秒)」で指定します。

mysql> select/*+ MAX_EXECUTION_TIME(1000) */count(*)from test_a;
ERROR 3024 (HY000): Query execution was interrupted, maximum statement execution time exceeded

なお、オプティマイザ・ヒントの文言を間違うと、ふつーにSELECTの処理が実行され、warningが検出されます。

以下、MAX_EXECUTON_TIMEのスペルミス(Iがない)をした例です。

mysql> select/*+ MAX_EXECUTON_TIME(1000) */count(*)from test_a;
+----------+
| count(*) |
+----------+
|  1048576 |
+----------+
1rowin set, 1 warning (18.93 sec)

注意?点

MAX_EXECUTION_TIMEは

  • 読み取り専用SELECTにしか効きません
  • ストアドプログラムには効きません

The execution timeout for SELECT statements, in milliseconds. If the value is 0, timeouts are not enabled.
max_execution_time applies as follows:

  • The global max_execution_time value provides the default for the session value for new connections. The session value applies to SELECT executions executed within the session that include no MAX_EXECUTION_TIME(N) optimizer hint or for which N is 0.
  • max_execution_time applies to read-only SELECT statements. Statements that are not read only are those that invoke a stored function that modifies data as a side effect.
  • max_execution_time is ignored for SELECT statements in stored programs.

https://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_max_execution_time

ということで。

トランザクションの扱いはどうなる?

「AutoCommit=OFF」の時、START TRANSACTIONを明示的に発行しなくても、最初のSQLを投げるとトランザクションが開始される話は、以前に触れました。

で、MAX_EXECUTION_TIMEでタイムアウトを引き起こした場合、どうなるか。

トランザクションは継続したままでした。
ま、予想通りでしたが。

MySQLトランザクション、基本的には、エラーでもトランザクションの状態は変わらないですね。
基本的ではないところだと、「デッドロックは、検出したらトランザクションが小さい方(=更新量の少ない方)を強制ロールバックさせる」です。


まとめ

  • MySQL 5.7以降、SELECTの実行時間を「MAX_EXECUTION_TIME」でタイムアウト制御することができる
  • システム変数だけじゃなく、オプティマイザ・ヒント(5.7新機能)により、SELECT文単位でもタイムアウト指定できる。
  • あくまで、「読み取り専用SELECT文だけ」が対象。
  • もともとMySQLの持ってるタイムアウト設定パラメータ、多い&名前が直感的じゃないの、ツラい。


MySQL5.7、GAしてから早2年ですよ。
ネット界隈はMySQL8.0で一喜一憂してますが、レイトマジョリティ以降の現場でも、さすがにMySQL5.7には手を出していいよね。

詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE)

詳解MySQL 5.7 止まらぬ進化に乗り遅れないためのテクニカルガイド (NEXT ONE)

ところで

OracleとかPostgreSQLってどうだっけ?


Viewing all articles
Browse latest Browse all 172

Trending Articles