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のための処理が完全に終わった後です。
というわけで、謎はとけたかしらん?