在 MySQL 8 中,错误信息通过网络发送给客户端的过程涉及多个步骤,主要包括错误信息的生成、格式化、以及通过网络协议(如 TCP/IP)将错误信息发送给客户端。以下是详细的流程和相关代码分析:
1. 错误信息的生成
当 MySQL 服务器遇到错误时,会调用 my_error
函数来生成错误信息。my_error
函数会根据错误编号(nr
)和格式化参数(MyFlags
)生成具体的错误信息。
my_error
函数
cpp
void my_error(int nr, myf MyFlags, ...) {const char *format;char ebuff[ERRMSGSIZE];DBUG_TRACE;DBUG_PRINT("my", ("nr: %d MyFlags: %d errno: %d", nr, MyFlags, errno));if (!(format = my_get_err_msg(nr)))(void)snprintf(ebuff, sizeof(ebuff), "Unknown error %d", nr);else {va_list args;va_start(args, MyFlags);(void)vsnprintf(ebuff, sizeof(ebuff), format, args);va_end(args);}/*Since this function is an error function, it will frequently be givenvalues that are too long (and thus truncated on byte boundaries,not code point or grapheme boundaries), values that are binary, etc.. Go through and replace every malformed UTF-8 byte with a question mark,so that the result is safe to send to the client and makes sense to readfor the user.*/for (char *ptr = ebuff, *end = ebuff + strlen(ebuff); ptr != end;) {my_wc_t ignored;int len = my_mb_wc_utf8mb4(&ignored, pointer_cast<const uchar *>(ptr),pointer_cast<const uchar *>(end));if (len > 0) {ptr += len;} else {*ptr++ = '?';}}(*error_handler_hook)(nr, ebuff, MyFlags);
}
2. 错误信息的处理
my_error
函数会调用 error_handler_hook
,这是一个全局变量,指向处理错误信息的函数。在 MySQL 服务器中,error_handler_hook
通常被设置为 my_message_sql
函数。
my_message_sql
函数
cpp
void my_message_sql(uint error, const char *str, myf MyFlags) {THD *thd = current_thd;DBUG_TRACE;DBUG_PRINT("error", ("error: %u message: '%s'", error, str));assert(str != nullptr);if (error == 0) {/* At least, prevent new abuse ... */assert(strncmp(str, "MyISAM table", 12) == 0);error = ER_UNKNOWN_ERROR;}if (thd) {(void)thd->raise_condition(error, nullptr, Sql_condition::SL_ERROR, str,MyFlags & ME_FATALERROR);}if (MyFlags & ME_ERRORLOG) {if (error < ER_SERVER_RANGE_START) {LogEvent().type(LOG_TYPE_ERROR).prio(ERROR_LEVEL).errcode(ER_ERROR_INFO_FROM_DA).lookup(ER_ERROR_INFO_FROM_DA, error, str);} else {LogEvent().type(LOG_TYPE_ERROR).prio(ERROR_LEVEL).errcode(error).verbatim(str);}} else if (!thd) {LogEvent().type(LOG_TYPE_ERROR).subsys(LOG_SUBSYSTEM_TAG).prio(ERROR_LEVEL).errcode((error < ER_SERVER_RANGE_START)? ER_SERVER_NO_SESSION_TO_SEND_TO: error).lookup(ER_SERVER_NO_SESSION_TO_SEND_TO, error, str);}
}
3. 错误信息发送给客户端
在 my_message_sql
函数中,如果存在一个连接的客户端(thd
不为 nullptr
),会调用 thd->raise_condition
方法来处理错误信息。raise_condition
方法会将错误信息发送给客户端。
C++源码
##
int mysqld_main(int argc, char **argv) {
error_handler_hook = my_message_sql;
}
##
##
/**Fill in and print a previously registered error message.@noteGoes through the (sole) function registered in error_handler_hook@param nr error number@param MyFlags Flags@param ... variable list matching that error format string
*/void my_error(int nr, myf MyFlags, ...) {const char *format;char ebuff[ERRMSGSIZE];DBUG_TRACE;DBUG_PRINT("my", ("nr: %d MyFlags: %d errno: %d", nr, MyFlags, errno));if (!(format = my_get_err_msg(nr)))(void)snprintf(ebuff, sizeof(ebuff), "Unknown error %d", nr);else {va_list args;va_start(args, MyFlags);(void)vsnprintf(ebuff, sizeof(ebuff), format, args);va_end(args);}/*Since this function is an error function, it will frequently be givenvalues that are too long (and thus truncated on byte boundaries,not code point or grapheme boundaries), values that are binary, etc..Go through and replace every malformed UTF-8 byte with a question mark,so that the result is safe to send to the client and makes sense to readfor the user.*/for (char *ptr = ebuff, *end = ebuff + strlen(ebuff); ptr != end;) {my_wc_t ignored;int len = my_mb_wc_utf8mb4(&ignored, pointer_cast<const uchar *>(ptr),pointer_cast<const uchar *>(end));if (len > 0) {ptr += len;} else {*ptr++ = '?';}}(*error_handler_hook)(nr, ebuff, MyFlags);
}
##
/**All global error messages are sent here where the first one is storedfor the client.
*/
/* ARGSUSED */
extern "C" void my_message_sql(uint error, const char *str, myf MyFlags);void my_message_sql(uint error, const char *str, myf MyFlags) {THD *thd = current_thd;DBUG_TRACE;DBUG_PRINT("error", ("error: %u message: '%s'", error, str));assert(str != nullptr);/*An error should have a valid error number (!= 0), so it can be caughtin stored procedures by SQL exception handlers.Calling my_error() with error == 0 is a bug.Remaining known places to fix:- storage/myisam/mi_create.c, my_printf_error()TODO:assert(error != 0);*/if (error == 0) {/* At least, prevent new abuse ... */assert(strncmp(str, "MyISAM table", 12) == 0);error = ER_UNKNOWN_ERROR;}/* Caller wishes to inform client, and one is attached. */if (thd) {(void)thd->raise_condition(error, nullptr, Sql_condition::SL_ERROR, str,MyFlags & ME_FATALERROR);/*Now for an argument check.We're asserting after rather than before raising thecondition to make the culprit easier to track down.Messages intended for the error-log are in the rangestarting at ER_SERVER_RANGE_START (error_code 10,000);messages intended for sending to a client are in therange below ER_SERVER_RANGE_START. If a message is tobe sent to both a client and the error log, it mustbe added twice (once in each range), and two separatecalls (e.g. my_error() and LogErr()) must be added tothe code.Only error-codes from the client range should be seenin this if(). If your patch asserts here, one of twothings probably happened:- You added a new message to messages_to_error_log.txt:The message was added to the server range, but codewas added that tries to send the message to a client(my_error(), push_warning_printf(), etc.).=> Move the new message to messages_to_clients.txt.The copied message should be added at the end ofthe range for the lowest server version you're addingthe message to.Rebuild the server; rerun your test.- You used an existing message:The existing message is intended for use withthe error-log (it appears in messages_to_error_log.txt),but the new code tries to send it to a client (my_error(),push_warning_printf(), etc.).=> Copy the existing message to messages_to_clients.txt.- The copied message should be added at the end ofthe range for the lowest server version you're addingthe message to.- The copied message will need its own symbol;if in doubt, call the copy of ER_EXAMPLE_MESSAGEER_DA_EXAMPLE_MESSAGE (as this version is for usewith the diagnostics area).Then make sure that your new code referencesthis new symbol when it sends the messageto a client.Rebuild the server; rerun your test.We'll assert this here (rather than in raise_condition) asSQL's SIGNAL command also calls raise_condition, and SIGNALis currently allowed to set any error-code (regardless ofrange). SIGNALing an error-code from the error-log rangewill not result in writing to that log to prevent abuse.*/assert(error < ER_SERVER_RANGE_START);}/* When simulating OOM, skip writing to error log to avoid mtr errors */DBUG_EXECUTE_IF("simulate_out_of_memory", return;);/*Caller wishes to send to both the client and the error-log.This is legacy behaviour that is no longer legal as errors flaggedto a client and those sent to the error-log are in differentnumeric ranges now. If you own code that does this, see aboutupdating it by splitting it into two calls, one sending statusto the client, the other sending it to the error-log usingLogErr() and friends.*/if (MyFlags & ME_ERRORLOG) {/*We've removed most uses of ME_ERRORLOG in the server.This leaves three possible cases, in which we'll rewritethe error-code from one in the client-range to one inthe error-log range here:- EE_OUTOFMEMORY: Correct to ER_SERVER_OUT_OF_RESOURCES somysys can remain logger-agnostic.- HA_* range: Correct to catch-all ER_SERVER_HANDLER_ERROR.- otherwise: Flag as using info from the diagnostics area(ER_ERROR_INFO_FROM_DA). This is a failsafe;if your code triggers it, your code is probablywrong.*/if ((error == EE_OUTOFMEMORY) || (error == HA_ERR_OUT_OF_MEM))error = ER_SERVER_OUT_OF_RESOURCES;else if (error <= HA_ERR_LAST)error = ER_SERVER_HANDLER_ERROR;if (error < ER_SERVER_RANGE_START)LogEvent().type(LOG_TYPE_ERROR).prio(ERROR_LEVEL).errcode(ER_ERROR_INFO_FROM_DA).lookup(ER_ERROR_INFO_FROM_DA, error, str);elseLogEvent().type(LOG_TYPE_ERROR).prio(ERROR_LEVEL).errcode(error).verbatim(str);/*This is no longer supported behaviour except for the casesoutlined above, so flag anything else in debug builds!(We're bailing after rather than before printing to make theculprit easier to track down.)*/assert((error == ER_FEATURE_NOT_AVAILABLE) ||(error >= ER_SERVER_RANGE_START));}/*Caller wishes to send to client, but none is attached, so we sendto error-log instead.*/else if (!thd) {LogEvent().type(LOG_TYPE_ERROR).subsys(LOG_SUBSYSTEM_TAG).prio(ERROR_LEVEL).errcode((error < ER_SERVER_RANGE_START)? ER_SERVER_NO_SESSION_TO_SEND_TO: error).lookup(ER_SERVER_NO_SESSION_TO_SEND_TO, error, str);}
}
##gdb调用栈
#0 my_message_sql (error=1118,str=0x71f16e5fa830 "Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs", MyFlags=0) at /home/yym/mysql8/mysql-8.1.0/sql/mysqld.cc:3830
#1 0x00005f5a64be63bc in my_error (nr=1118, MyFlags=0) at /home/yym/mysql8/mysql-8.1.0/mysys/my_error.cc:249
#2 0x00005f5a63444e00 in mysql_prepare_create_table (thd=0x71f078000f10, error_schema_name=0x71f0783d2018 "grey", error_table_name=0x71f0783a1010 "o2o_hot_marketing_test",create_info=0x71f16e5fc2a0, alter_info=0x71f16e5fc3e0, file=0x71f0784426f0, is_partitioned=false, key_info_buffer=0x71f16e5fb470, key_count=0x71f16e5fb468,fk_key_info_buffer=0x71f16e5fb450, fk_key_count=0x71f16e5fb44c, existing_fks=0x0, existing_fks_count=0, existing_fks_table=0x0, fk_max_generated_name_number=0,select_field_count=0, find_parent_keys=true) at /home/yym/mysql8/mysql-8.1.0/sql/sql_table.cc:8349
#3 0x00005f5a63446fa2 in create_table_impl (thd=0x71f078000f10, schema=..., db=0x71f0783d2018 "grey", table_name=0x71f0783a1010 "o2o_hot_marketing_test",error_table_name=0x71f0783a1010 "o2o_hot_marketing_test", path=0x71f16e5fb550 "./grey/o2o_hot_marketing_test", create_info=0x71f16e5fc2a0, alter_info=0x71f16e5fc3e0,internal_tmp_table=false, select_field_count=0, find_parent_keys=true, no_ha_table=false, do_not_store_in_dd=false, is_trans=0x71f16e5fb7d7, key_info=0x71f16e5fb470,key_count=0x71f16e5fb468, keys_onoff=Alter_info::ENABLE, fk_key_info=0x71f16e5fb450, fk_key_count=0x71f16e5fb44c, existing_fk_info=0x0, existing_fk_count=0,existing_fk_table=0x0, fk_max_generated_name_number=0, table_def=0x71f16e5fb458, post_ddl_ht=0x71f16e5fb7e0) at /home/yym/mysql8/mysql-8.1.0/sql/sql_table.cc:8916
#4 0x00005f5a6344835d in mysql_create_table_no_lock (thd=0x71f078000f10, db=0x71f0783d2018 "grey", table_name=0x71f0783a1010 "o2o_hot_marketing_test",create_info=0x71f16e5fc2a0, alter_info=0x71f16e5fc3e0, select_field_count=0, find_parent_keys=true, is_trans=0x71f16e5fb7d7, post_ddl_ht=0x71f16e5fb7e0)at /home/yym/mysql8/mysql-8.1.0/sql/sql_table.cc:9231
#5 0x00005f5a6344bf2d in mysql_create_table (thd=0x71f078000f10, create_table=0x71f0783d19c0, create_info=0x71f16e5fc2a0, alter_info=0x71f16e5fc3e0)at /home/yym/mysql8/mysql-8.1.0/sql/sql_table.cc:10148
#6 0x00005f5a63b44239 in Sql_cmd_create_table::execute (this=0x71f0783d3508, thd=0x71f078000f10) at /home/yym/mysql8/mysql-8.1.0/sql/sql_cmd_ddl_table.cc:435
#7 0x00005f5a63366fbb in mysql_execute_command (thd=0x71f078000f10, first_level=true) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:3736
#8 0x00005f5a6336ccb3 in dispatch_sql_command (thd=0x71f078000f10, parser_state=0x71f16e5fd9f0) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:5447
#9 0x00005f5a633620d7 in dispatch_command (thd=0x71f078000f10, com_data=0x71f16e5fe340, command=COM_QUERY) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:2112
#10 0x00005f5a6335ff77 in do_command (thd=0x71f078000f10) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:1459
#11 0x00005f5a635b7835 in handle_connection (arg=0x5f5a6c557d70) at /home/yym/mysql8/mysql-8.1.0/sql/conn_handler/connection_handler_per_thread.cc:303
#12 0x00005f5a654f6bdc in pfs_spawn_thread (arg=0x5f5a6c52c670) at /home/yym/mysql8/mysql-8.1.0/storage/perfschema/pfs.cc:3043
#13 0x000071f17d494ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#14 0x000071f17d526850 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
##gdb调用栈
(gdb) bt
#0 Protocol_classic::send_error (this=0x71f0784347c0, sql_errno=1118,err_msg=0x71f078003960 "Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs", sql_state=0x71f078003b60 "42000") at /home/yym/mysql8/mysql-8.1.0/sql/protocol_classic.cc:1344
#1 0x00005f5a63263b3d in THD::send_statement_status (this=0x71f078000f10) at /home/yym/mysql8/mysql-8.1.0/sql/sql_class.cc:2904
#2 0x00005f5a6336379e in dispatch_command (thd=0x71f078000f10, com_data=0x71f16e5fe340, command=COM_QUERY) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:2501
#3 0x00005f5a6335ff77 in do_command (thd=0x71f078000f10) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:1459
#4 0x00005f5a635b7835 in handle_connection (arg=0x5f5a6c557d70) at /home/yym/mysql8/mysql-8.1.0/sql/conn_handler/connection_handler_per_thread.cc:303
#5 0x00005f5a654f6bdc in pfs_spawn_thread (arg=0x5f5a6c52c670) at /home/yym/mysql8/mysql-8.1.0/storage/perfschema/pfs.cc:3043
#6 0x000071f17d494ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#7 0x000071f17d526850 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81
##gdb调用栈网络发送sql错误信息
(gdb) bt
#0 net_write_buff (net=0x71f078002d48,packet=0x71f16e5fd500 "^\004#42000Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLO"..., len=202) at /home/yym/mysql8/mysql-8.1.0/sql-common/net_serv.cc:937
#1 0x00005f5a635883ac in net_write_command (net=0x71f078002d48, command=255 '\377', header=0x5f5a664abad0 "", head_len=0,packet=0x71f16e5fd500 "^\004#42000Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLO"..., len=202) at /home/yym/mysql8/mysql-8.1.0/sql-common/net_serv.cc:907
#2 0x00005f5a63abb524 in net_send_error_packet (net=0x71f078002d48, sql_errno=1118,err=0x71f078003960 "Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs", sqlstate=0x71f078003b60 "42000", bootstrap=false, client_capabilities=2147656357,character_set_results=0x5f5a6867d520 <my_charset_utf8mb3_general_ci>) at /home/yym/mysql8/mysql-8.1.0/sql/protocol_classic.cc:1236
#3 0x00005f5a63abb31d in net_send_error_packet (thd=0x71f078000f10, sql_errno=1118,err=0x71f078003960 "Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs", sqlstate=0x71f078003b60 "42000") at /home/yym/mysql8/mysql-8.1.0/sql/protocol_classic.cc:1177
#4 0x00005f5a63abb965 in Protocol_classic::send_error (this=0x71f0784347c0, sql_errno=1118,err_msg=0x71f078003960 "Row size too large. The maximum row size for the used table type, not counting BLOBs, is 65535. This includes storage overhead, check the manual. You have to change some columns to TEXT or BLOBs", sql_state=0x71f078003b60 "42000") at /home/yym/mysql8/mysql-8.1.0/sql/protocol_classic.cc:1347
#5 0x00005f5a63263b3d in THD::send_statement_status (this=0x71f078000f10) at /home/yym/mysql8/mysql-8.1.0/sql/sql_class.cc:2904
#6 0x00005f5a6336379e in dispatch_command (thd=0x71f078000f10, com_data=0x71f16e5fe340, command=COM_QUERY) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:2501
#7 0x00005f5a6335ff77 in do_command (thd=0x71f078000f10) at /home/yym/mysql8/mysql-8.1.0/sql/sql_parse.cc:1459
#8 0x00005f5a635b7835 in handle_connection (arg=0x5f5a6c557d70) at /home/yym/mysql8/mysql-8.1.0/sql/conn_handler/connection_handler_per_thread.cc:303
#9 0x00005f5a654f6bdc in pfs_spawn_thread (arg=0x5f5a6c52c670) at /home/yym/mysql8/mysql-8.1.0/storage/perfschema/pfs.cc:3043
#10 0x000071f17d494ac3 in start_thread (arg=<optimized out>) at ./nptl/pthread_create.c:442
#11 0x000071f17d526850 in clone3 () at ../sysdeps/unix/sysv/linux/x86_64/clone3.S:81