GA

2018/09/27

「max_allowed_packetの基本的な動き」がどうしてそうなるのかのはなし

TL;DR

  • 1SQLのサイズ上限は、max_allowed_packetとnet_buffer_lengthで制御される。
  • max_allowed_packetはグローバルにデフォルト値を持ち、コネクション確立時にセッションにコピーされ、セッション変数は読み込み専用である。(セッションに適用するにはconnectなど再接続が必要)
  • net_buffer_lengthはグローバルにデフォルト値を持ち、コネクション確立時にセッションにコピーされ、セッション変数も変更可能である。
  • net_buffer_length => max_allowed_packetの場合、net_buffer_lengthが1SQLの上限値になる。
  • net_buffer_length < max_allowed_packetの場合、max_allowed_packetが1SQLの上限値になる。
とまとめられているが、これは

ということ。

まず、 max_allowed_packet の上限に引っかかった時に出るエラーである、 “Got a packet bigger than ‘max_allowed_packet’ bytes” からたどっていくことにする。
(See also Dive into MySQL Error - Speaker Deck
ソースコードは5.7.23のものにしていますよん。
$ grep "max_allowed_packet" include/mysqld_ername.h
{ "ER_NET_PACKET_TOO_LARGE", 1153, "Got a packet bigger than \'max_allowed_packet\' bytes" },
{ "ER_TOO_LONG_STRING", 1162, "Result string is longer than \'max_allowed_packet\' bytes" },
{ "ER_WARN_ALLOWED_PACKET_OVERFLOWED", 1301, "Result of %s() was larger than max_allowed_packet (%ld) - truncated" },
MySQLへのJDBC接続で、とあるバグを踏むまでの話 -(1)「max_allowed_packet」の基本的な働き - なからなLife ではバルクインサートだったので、 ER_NET_PACKET_TOO_LARGE の方だと思う。
$ grep -r ER_NET_PACKET_TOO_LARGE . | grep -v mysql-test
./include/sql_state.h:{ ER_NET_PACKET_TOO_LARGE                 ,"08S01", "" },
./include/mysqld_ername.h:{ "ER_NET_PACKET_TOO_LARGE", 1153, "Got a packet bigger than \'max_allowed_packet\' bytes" },
./include/mysqld_error.h:#define ER_NET_PACKET_TOO_LARGE 1153
./libmysql/libmysql.c:      else if (net->last_errno == ER_NET_PACKET_TOO_LARGE)
./sql-common/client.c:    set_mysql_error(mysql, net->last_errno == ER_NET_PACKET_TOO_LARGE ?
./sql-common/client.c:    if (net->last_errno == ER_NET_PACKET_TOO_LARGE)
./sql/net_serv.cc:    net->last_errno= ER_NET_PACKET_TOO_LARGE;
./sql/net_serv.cc:    my_error(ER_NET_PACKET_TOO_LARGE, MYF(0));
./sql/rpl_rli_pdb.cc:    with error ER_NET_PACKET_TOO_LARGE.
./sql/rpl_slave.cc:          mi->report(ERROR_LEVEL, ER_NET_PACKET_TOO_LARGE,
./sql/share/errmsg-utf8.txt:ER_NET_PACKET_TOO_LARGE 08S01
./include のやつは定義系だし、 ./libmysql, ./sql-common のやつは == で比較しているのでこのエラーを受け取った後にどうするかの処理だろう。
./sql/rpl_* はレプリケーション関連なので取り敢えずパスして、 ./sql/net_serv.cc の中で代入しているからここが本丸のような気がする。
grepで引っかかった2行は net_realloc という関数の中に入っていて、名前から察するにここが
パケットメッセージバッファーは net_buffer_length バイトに初期化されますが、必要に応じて max_allowed_packet バイトまで大きくできます。
のパケットメッセージバッファーを大きくする処理なのであろう。
コイツが呼ばれているところを探しに行く。
$ grep -r net_realloc .
./include/mysql.h.pp:my_bool net_realloc(NET *net, size_t length);
./include/mysql_com.h:my_bool net_realloc(NET *net, size_t length);
./libmysql/libmysql.c:    res= net_realloc(net, buf_length + length);
./sql/net_serv.cc:my_bool net_realloc(NET *net, size_t length)
./sql/net_serv.cc:  DBUG_ENTER("net_realloc");
./sql/net_serv.cc:      must match the size of the buffer allocated in net_realloc().
./sql/net_serv.cc:  if ((pkt_data_len >= net->max_packet) && net_realloc(net, pkt_data_len))
./libmysql とはlibmysqlclient.so(Connector/C)のコードなので外すとするとまだ同じ ./sql/net_serv.cc の行。開いてみると net_read_packet の中で、「今のパケットメッセージバッファーよりもデータが大きければ net_realloc 」な記述に当たる。
というわけで、
  • net_buffer_length => max_allowed_packetの場合、net_buffer_lengthが1SQLの上限値になる。
の理由は、 net_buffer_length のサイズでアロケートされたパケットメッセージバッファが足りなくなって拡張しようとした時に初めて max_allowed_packet と比較されてエラーに分岐するから、でした。
Cこわくないよ!
(あっdbts2018のブログがまだ下書きn

0 件のコメント :

コメントを投稿