thd->proc_info="Sending data"について

MySQLユーザ会MLにて面白そうなネタ(質問投稿)があったので、食いつきちゅう。

# mixiの方にかいたけど、gdb中にフリーズ、かなしす。。。気をとりなおして再開。

thd->proc_info="Sendind data"をしているのはsql/sql_select.ccの1827行目。JOIN::exec関数。とりあえずthd->proc_infoが次の状態に移るまでをトレースすることに。

Breakpoint 1, JOIN::exec (this=0x8b017c0) at sql_select.cc:1827
(gdb) bt
#0  JOIN::exec (this=0x8b017c0) at sql_select.cc:1827
#1  0x082005bc in mysql_select (thd=0x8ae1a68, rref_pointer_array=0x8ae1e58, tables=0x8b01160, wild_num=1, fields=@0x8ae1dbc, conds=0x8b01698, og_num=0, order=0x0, group=0x0, having=0x0,
    proc_param=0x0, select_options=2156153344, result=0x8b017b0, unit=0x8ae1b08, select_lex=0x8ae1d30) at sql_select.cc:1999
#2  0x08203d4f in handle_select (thd=0x8ae1a68, lex=0x8ae1aa4, result=0x8b017b0, setup_tables_done_option=0) at sql_select.cc:254
#3  0x081a935b in mysql_execute_command (thd=0x8ae1a68) at sql_parse.cc:2589
#4  0x081b12b2 in mysql_parse (thd=0x8ae1a68, inBuf=0x8b01030 "select * from t1 where c1 > 200 and c1 < 250 limit 10", length=53) at sql_parse.cc:5879
#5  0x081b337d in dispatch_command (command=COM_QUERY, thd=0x8ae1a68, packet=0x8af9001 "select * from t1 where c1 > 200 and c1 < 250 limit 10", packet_length=54) at sql_parse.cc:1768
#6  0x081b4685 in do_command (thd=0x8ae1a68) at sql_parse.cc:1552
#7  0x081b53ed in handle_one_connection (arg=0x8ae1a68) at sql_parse.cc:1183
#8  0xa7c16240 in start_thread () from /lib/tls/i686/cmov/libpthread.so.0
#9  0xa7b4132e in clone () from /lib/tls/i686/cmov/libc.so.6

で、まずは分かったことの概要から。

thd->proc_infoの遷移順(Sending dataの前後あたり抜粋)

  • preparing
  • Sending data
  • end
  • query end

"Sending data"の直後にすること。

    thd->proc_info="Sending data";
    DBUG_PRINT("info", ("%s", thd->proc_info));
    result->send_fields((procedure ? curr_join->procedure_fields_list :
                         *curr_fields_list),
                        Protocol::SEND_NUM_ROWS | Protocol::SEND_EOF);
    error= do_select(curr_join, curr_fields_list, NULL, procedure);
    thd->limit_found_rows= curr_join->send_records;
    thd->examined_row_count= curr_join->examined_rows;

変数resultはselect_sendオブジェクト、send_fields関数はProtocol::send_fields関数をラップしているだけ。

というわけで、"Sending data"と状態が変わってから、結果セットのフィールド型情報(メタデータ)を送信してる。

MySQLでは、結果セットをサーバからクライアントへ返す際、結果データの送信よりも前に、フィールド型情報を送る。これから送るデータがそれぞれどんなデータなのか、文字コードは何かとかそういう情報。これはMySQL client-serverプロトコルで決められていること。興味のあるひとはInternals Manualを読むがよろし。

で、次にdo_selectを呼び出している。つーまーりー、つーまーりー、"Sending data"になってからHandler APIの呼出しをするんじゃね?という話だーよ。

do_selectをさらに掘っていくと、、、、、handler API貫通キター!!!! rr_quickもキター!!!!

#0  mi_rnext (info=0x8b054c0, buf=0x8ae3758 "ýe", inx=0) at mi_rnext.c:34
#1  0x08263105 in ha_myisam::index_next (this=0x8ae3660, buf=0x8ae3758 "ýe") at ha_myisam.cc:1221
#2  0x0825b549 in handler::read_range_next (this=0x8ae3660) at handler.cc:2663
#3  0x0825b1ca in handler::read_multi_range_next (this=0x8ae3660, found_range_p=0xa17f41a0) at handler.cc:2543
#4  0x08244473 in QUICK_RANGE_SELECT::get_next (this=0x8acb8a8) at opt_range.cc:6671
#5  0x08256b75 in rr_quick (info=0x8b02ba4) at records.cc:227
#6  0x081ebf86 in sub_select (join=0x8b017c0, join_tab=0x8b02b68, end_of_records=false) at sql_select.cc:10081
#7  0x081f2f33 in do_select (join=0x8b017c0, fields=0x8ae1dbc, table=0x0, procedure=0x0) at sql_select.cc:9812
#8  0x08203b67 in JOIN::exec (this=0x8b017c0) at sql_select.cc:1832
#9  0x082005bc in mysql_select (thd=0x8ae1a68, rref_pointer_array=0x8ae1e58, tables=0x8b01160, wild_num=1, fields=@0x8ae1dbc, conds=0x8b01698, og_num=0, order=0x0, group=0x0, having=0x0,
    proc_param=0x0, select_options=2156153344, result=0x8b017b0, unit=0x8ae1b08, select_lex=0x8ae1d30) at sql_select.cc:1999
#10 0x08203d4f in handle_select (thd=0x8ae1a68, lex=0x8ae1aa4, result=0x8b017b0, setup_tables_done_option=0) at sql_select.cc:254
#11 0x081a935b in mysql_execute_command (thd=0x8ae1a68) at sql_parse.cc:2589
#12 0x081b12b2 in mysql_parse (thd=0x8ae1a68, inBuf=0x8b01030 "select * from t1 where c1 > 100 and c1 < 200 limit 10", length=53) at sql_parse.cc:5879
#13 0x081b337d in dispatch_command (command=COM_QUERY, thd=0x8ae1a68, packet=0x8af9001 "", packet_length=54) at sql_parse.cc:1768
#14 0x081b4685 in do_command (thd=0x8ae1a68) at sql_parse.cc:1552
#15 0x081b53ed in handle_one_connection (arg=0x8ae1a68) at sql_parse.cc:1183
#16 0xa7c05240 in start_thread () from /lib/tls/i686/cmov/libpthread.so.0
#17 0xa7b3032e in clone () from /lib/tls/i686/cmov/libc.so.6

とりあえず「"Sending data"にしてからHandler APIを呼ぶ」は正解と。

次、Protocol APIを呼んでいるのはどこかなー。

#0  Protocol::write (this=0x8ae251c) at protocol.cc:698
#1  0x08184a49 in select_send::send_data (this=0x8b017b0, items=@0x8ae1dbc) at sql_class.cc:969
#2  0x081e515e in end_send (join=0x8b017c0, join_tab=0x8b02ccc, end_of_records=false) at sql_select.cc:10929
#3  0x081ebd3b in evaluate_join_record (join=0x8b017c0, join_tab=0x8b02b68, error=0, report_error=0x8ae24f4 "") at sql_select.cc:10191
#4  0x081ebf64 in sub_select (join=0x8b017c0, join_tab=0x8b02b68, end_of_records=false) at sql_select.cc:10073
#5  0x081f2f33 in do_select (join=0x8b017c0, fields=0x8ae1dbc, table=0x0, procedure=0x0) at sql_select.cc:9812
#6  0x08203b67 in JOIN::exec (this=0x8b017c0) at sql_select.cc:1832
#7  0x082005bc in mysql_select (thd=0x8ae1a68, rref_pointer_array=0x8ae1e58, tables=0x8b01160, wild_num=1, fields=@0x8ae1dbc, conds=0x8b01698, og_num=0, order=0x0, group=0x0, having=0x0,
    proc_param=0x0, select_options=2156153344, result=0x8b017b0, unit=0x8ae1b08, select_lex=0x8ae1d30) at sql_select.cc:1999
#8  0x08203d4f in handle_select (thd=0x8ae1a68, lex=0x8ae1aa4, result=0x8b017b0, setup_tables_done_option=0) at sql_select.cc:254
#9  0x081a935b in mysql_execute_command (thd=0x8ae1a68) at sql_parse.cc:2589
#10 0x081b12b2 in mysql_parse (thd=0x8ae1a68, inBuf=0x8b01030 "select * from t1 where c1 > 100 and c1 < 200 limit 10", length=53) at sql_parse.cc:5879
#11 0x081b337d in dispatch_command (command=COM_QUERY, thd=0x8ae1a68, packet=0x8af9001 "", packet_length=54) at sql_parse.cc:1768
#12 0x081b4685 in do_command (thd=0x8ae1a68) at sql_parse.cc:1552
#13 0x081b53ed in handle_one_connection (arg=0x8ae1a68) at sql_parse.cc:1183
#14 0xa7c05240 in start_thread () from /lib/tls/i686/cmov/libpthread.so.0
#15 0xa7b3032e in clone () from /lib/tls/i686/cmov/libc.so.6

これだね。

さらにちょい追加調査。

select_send::send_data関数は、、、(抜粋)

  protocol->prepare_for_resend();
  Item *item;
  while ((item=li++))
  {
    if (item->send(protocol, &buffer))
    {
      protocol->free();                         // Free used buffer
      my_message(ER_OUT_OF_RESOURCES, ER(ER_OUT_OF_RESOURCES), MYF(0));
      break;
    }
  }

1行分のデータを送信する。

sub_select関数にて、、、

  while (rc == NESTED_LOOP_OK)
  {
    error= info->read_record(info);
    rc= evaluate_join_record(join, join_tab, error, report_error);
  }

evaluate_join_recordが「次もまだあるよ」と返す限りひたすら、read_recordを呼んで、evaluate_join_recordでデータ送信+次判定。read_recordってのは、、、、、関数ポインタで、実体は、"rr_quickもキター"と上の方のbacktraceのところで書いたように、Handler APIを条件に併せて呼び出すやつ。read_recordに突っ込まれているもの頻繁に変わるとおもふ。

ということで、"Sending data"中に、、ストレージエンジンから1行読んで、1行分送信、そして次の1行を読んで、、、とやってるわけです。

# ネットワークレベルでの送信は、送信バッファにため込んでから行うけどね。

# ちなみに、結果データが送信された後、eofパケットがサーバからクライアントに送られます。mysqlクライアントはeofパケットを受け取るまで、結果セットを表示したりはしません。
# eofパケットの送信は、上記ループが終わった後に行われます。JOIN::exec関数の中かな?

はい、まだもうちょい続きます。

"Sending data"から"end"になるのはいつか。eofパケットを送信した直後はまだ"Sending data"のままです。

mysql_select関数の一番最後、

  if (free_join)
  {
    thd->proc_info="end";
    err|= select_lex->cleanup();
    DBUG_RETURN(err || thd->net.report_error);
  }
  DBUG_RETURN(join->error);

ここで"end"に代わります。この段階で既にクライアントに結果データは送信し終わってますが、サーバ側には処理過程で作成されたリソースがまだクリーンされずに残ってます。上記コードではselect_lex、つまりパーサによるSQL構文解析結果が入ったオブジェクトをクリーンしています。

でもって次、"end"から"query end"になるのはどこかですが、

mysql_execute_command関数の最後の方、

  case SQLCOM_XA_RECOVER:
    res= mysql_xa_recover(thd);
    break;
  default:
#ifndef EMBEDDED_LIBRARY
    DBUG_ASSERT(0);                             /* Impossible */
#endif
    send_ok(thd);
    break;
  }
  thd->proc_info="query end";

つまり、SQLCOM_SELECTのための処理が完全に終わった後です。

というわけで、謎はとけたかしらん?