Skip to content

Commit 295f3e4

Browse files
committed
MDEV-19237 Skip sending metadata when possible for binary protocol.
Do not resend metadata, if metadata does not change between prepare and execute of prepared statement, or between executes. Currently, metadata of *every* prepared statement will be checksummed, and change is detected once checksum changes. This is not from ideal, performance-wise. The code for better/faster detection of unchanged metadata, is already in place, but currently disabled due to PS bugs, such as MDEV-23913.
1 parent a62a675 commit 295f3e4

File tree

8 files changed

+450
-19
lines changed

8 files changed

+450
-19
lines changed

include/mysql_com.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,6 +305,9 @@ enum enum_indicator_type
305305
/* support of extended metadata (e.g. type/format information) */
306306
#define MARIADB_CLIENT_EXTENDED_METADATA (1ULL << 35)
307307

308+
/* Do not resend metadata for prepared statements, since 10.6*/
309+
#define MARIADB_CLIENT_CACHE_METADATA (1ULL << 36)
310+
308311
#ifdef HAVE_COMPRESS
309312
#define CAN_CLIENT_COMPRESS CLIENT_COMPRESS
310313
#else
@@ -345,8 +348,8 @@ enum enum_indicator_type
345348
CLIENT_CONNECT_ATTRS |\
346349
MARIADB_CLIENT_STMT_BULK_OPERATIONS |\
347350
MARIADB_CLIENT_EXTENDED_METADATA|\
351+
MARIADB_CLIENT_CACHE_METADATA |\
348352
CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS)
349-
350353
/*
351354
Switch off the flags that are optional and depending on build flags
352355
If any of the optional flags is supported by the build it will be switched

libmariadb

sql/mysqld.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7169,6 +7169,7 @@ SHOW_VAR status_vars[]= {
71697169
{"Max_used_connections", (char*) &max_used_connections, SHOW_LONG},
71707170
{"Memory_used", (char*) &show_memory_used, SHOW_SIMPLE_FUNC},
71717171
{"Memory_used_initial", (char*) &start_memory_used, SHOW_LONGLONG},
7172+
{"Resultset_metadata_skipped", (char *) offsetof(STATUS_VAR, skip_metadata_count),SHOW_LONG_STATUS},
71727173
{"Not_flushed_delayed_rows", (char*) &delayed_rows_in_use, SHOW_LONG_NOFLUSH},
71737174
{"Open_files", (char*) &my_file_opened, SHOW_SINT},
71747175
{"Open_streams", (char*) &my_stream_opened, SHOW_LONG_NOFLUSH},

sql/protocol.cc

Lines changed: 263 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,241 @@ bool Protocol_text::store_field_metadata(const THD * thd,
913913
}
914914

915915

916+
/*
917+
MARIADB_CLIENT_CACHE_METADATA support.
918+
919+
Bulk of the code below is dedicated to detecting whether column metadata has
920+
changed after prepare, or between executions of a prepared statement.
921+
922+
For some prepared statements, metadata can't change without going through
923+
Prepared_Statement::reprepare(), which makes detecting changes easy.
924+
925+
Others, "SELECT ?" & Co, are more fragile, and sensitive to input parameters,
926+
or user variables. Detecting metadata change for this class of PS is harder,
927+
we calculate signature (hash value), and check whether this changes between
928+
executions. This is a more expensive method.
929+
*/
930+
931+
932+
/**
933+
Detect whether column info can be changed without
934+
PS repreparing.
935+
936+
Such colum info is called fragile. The opposite of
937+
fragile is.
938+
939+
940+
@param it - Item representing column info
941+
@return true, if columninfo is "fragile", false if it is stable
942+
943+
944+
@todo does not work due to MDEV-23913. Currently,
945+
everything about prepared statements is fragile.
946+
*/
947+
948+
static bool is_fragile_columnifo(Item *it)
949+
{
950+
#define MDEV_23913_FIXED 0
951+
#if MDEV_23913_FIXED
952+
if (dynamic_cast<Item_param *>(it))
953+
return true;
954+
955+
if (dynamic_cast<Item_func_user_var *>(it))
956+
return true;
957+
958+
if (dynamic_cast <Item_sp_variable*>(it))
959+
return true;
960+
961+
/* Check arguments of functions.*/
962+
auto item_args= dynamic_cast<Item_args *>(it);
963+
if (!item_args)
964+
return false;
965+
auto args= item_args->arguments();
966+
auto arg_count= item_args->argument_count();
967+
for (uint i= 0; i < arg_count; i++)
968+
{
969+
if (is_fragile_columnifo(args[i]))
970+
return true;
971+
}
972+
return false;
973+
#else /* MDEV-23913 fixed*/
974+
return true;
975+
#endif
976+
}
977+
978+
979+
#define INVALID_METADATA_CHECKSUM 0
980+
981+
982+
/**
983+
Calculate signature for column info sent to the client as CRC32 over data,
984+
that goes into the column info packet.
985+
We assume that if checksum does not change, then column info was not
986+
modified.
987+
988+
@param thd THD
989+
@param list column info
990+
991+
@return CRC32 of the metadata
992+
*/
993+
994+
static uint32 calc_metadata_hash(THD *thd, List<Item> *list)
995+
{
996+
List_iterator_fast<Item> it(*list);
997+
Item *item;
998+
uint32 crc32_c= 0;
999+
while ((item= it++))
1000+
{
1001+
Send_field field(thd, item);
1002+
auto field_type= item->type_handler()->field_type();
1003+
auto charset= item->charset_for_protocol();
1004+
/*
1005+
The data below should contain everything that influences
1006+
content of the column info packet.
1007+
*/
1008+
LEX_CSTRING data[]=
1009+
{
1010+
field.table_name,
1011+
field.org_table_name,
1012+
field.col_name,
1013+
field.org_col_name,
1014+
field.db_name,
1015+
field.attr(MARIADB_FIELD_ATTR_DATA_TYPE_NAME),
1016+
field.attr(MARIADB_FIELD_ATTR_FORMAT_NAME),
1017+
{(const char *) &field.length, sizeof(field.length)},
1018+
{(const char *) &field.flags, sizeof(field.flags)},
1019+
{(const char *) &field.decimals, sizeof(field.decimals)},
1020+
{(const char *) &charset, sizeof(charset)},
1021+
{(const char *) &field_type, sizeof(field_type)},
1022+
};
1023+
for (const auto &chunk : data)
1024+
crc32_c= my_crc32c(crc32_c, chunk.str, chunk.length);
1025+
}
1026+
1027+
if (crc32_c == INVALID_METADATA_CHECKSUM)
1028+
return 1;
1029+
return crc32_c;
1030+
}
1031+
1032+
1033+
1034+
/**
1035+
Check if metadata columns have changed since last call to this
1036+
function.
1037+
1038+
@param send_column_info_state saved state, changed if the function
1039+
return true.
1040+
@param thd THD
1041+
@param list columninfo Items
1042+
@return true,if metadata columns have changed since last call,
1043+
false otherwise
1044+
*/
1045+
1046+
static bool metadata_columns_changed(send_column_info_state &state, THD *thd,
1047+
List<Item> &list)
1048+
{
1049+
if (!state.initialized)
1050+
{
1051+
state.initialized= true;
1052+
state.immutable= true;
1053+
Item *item;
1054+
List_iterator_fast<Item> it(list);
1055+
while ((item= it++))
1056+
{
1057+
if (is_fragile_columnifo(item))
1058+
{
1059+
state.immutable= false;
1060+
state.checksum= calc_metadata_hash(thd, &list);
1061+
break;
1062+
}
1063+
}
1064+
state.last_charset= thd->variables.character_set_client;
1065+
return true;
1066+
}
1067+
1068+
/*
1069+
Since column info can change under our feet, we use more expensive
1070+
checksumming to check if column metadata has not changed since last time.
1071+
*/
1072+
if (!state.immutable)
1073+
{
1074+
uint32 checksum= calc_metadata_hash(thd, &list);
1075+
if (checksum != state.checksum)
1076+
{
1077+
state.checksum= checksum;
1078+
state.last_charset= thd->variables.character_set_client;
1079+
return true;
1080+
}
1081+
}
1082+
1083+
/*
1084+
Character_set_client influences result set metadata, thus resend metadata
1085+
whenever it changes.
1086+
*/
1087+
if (state.last_charset != thd->variables.character_set_client)
1088+
{
1089+
state.last_charset= thd->variables.character_set_client;
1090+
return true;
1091+
}
1092+
1093+
return false;
1094+
}
1095+
1096+
1097+
/**
1098+
Determine whether column info must be sent to the client.
1099+
Skip column info, if client supports caching, and (prepared) statement
1100+
output fields have not changed.
1101+
1102+
@param thd THD
1103+
@param list column info
1104+
@param flags send flags. If Protocol::SEND_FORCE_COLUMN_INFO is set,
1105+
this function will return true
1106+
@return true, if column info must be sent to the client.
1107+
false otherwise
1108+
*/
1109+
1110+
static bool should_send_column_info(THD* thd, List<Item>* list, uint flags)
1111+
{
1112+
if (!(thd->client_capabilities & MARIADB_CLIENT_CACHE_METADATA))
1113+
{
1114+
/* Client does not support abbreviated metadata.*/
1115+
return true;
1116+
}
1117+
1118+
if (!thd->cur_stmt)
1119+
{
1120+
/* Neither COM_PREPARE nor COM_EXECUTE run.*/
1121+
return true;
1122+
}
1123+
1124+
if (thd->spcont)
1125+
{
1126+
/* Always sent full metadata from inside the stored procedure.*/
1127+
return true;
1128+
}
1129+
1130+
if (flags & Protocol::SEND_FORCE_COLUMN_INFO)
1131+
return true;
1132+
1133+
auto &column_info_state= thd->cur_stmt->column_info_state;
1134+
#ifndef DBUG_OFF
1135+
auto cmd= thd->get_command();
1136+
#endif
1137+
1138+
DBUG_ASSERT(cmd == COM_STMT_EXECUTE || cmd == COM_STMT_PREPARE);
1139+
DBUG_ASSERT(cmd != COM_STMT_PREPARE || !column_info_state.initialized);
1140+
1141+
bool ret= metadata_columns_changed(column_info_state, thd, *list);
1142+
1143+
DBUG_ASSERT(cmd != COM_STMT_PREPARE || ret);
1144+
if (!ret)
1145+
thd->status_var.skip_metadata_count++;
1146+
1147+
return ret;
1148+
}
1149+
1150+
9161151
/**
9171152
Send name and type of result to client.
9181153
@@ -938,30 +1173,44 @@ bool Protocol::send_result_set_metadata(List<Item> *list, uint flags)
9381173
Protocol_text prot(thd, thd->variables.net_buffer_length);
9391174
DBUG_ENTER("Protocol::send_result_set_metadata");
9401175

1176+
bool send_column_info= should_send_column_info(thd, list, flags);
1177+
9411178
if (flags & SEND_NUM_ROWS)
942-
{ // Packet with number of elements
943-
uchar buff[MAX_INT_WIDTH];
1179+
{
1180+
/*
1181+
Packet with number of columns.
1182+
1183+
Will also have a 1 byte column info indicator, in case
1184+
MARIADB_CLIENT_CACHE_METADATA client capability is set.
1185+
*/
1186+
uchar buff[MAX_INT_WIDTH+1];
9441187
uchar *pos= net_store_length(buff, list->elements);
1188+
if (thd->client_capabilities & MARIADB_CLIENT_CACHE_METADATA)
1189+
*pos++= (uchar)send_column_info;
1190+
9451191
DBUG_ASSERT(pos <= buff + sizeof(buff));
9461192
if (my_net_write(&thd->net, buff, (size_t) (pos-buff)))
9471193
DBUG_RETURN(1);
9481194
}
9491195

1196+
if (send_column_info)
1197+
{
9501198
#ifndef DBUG_OFF
951-
field_handlers= (const Type_handler**) thd->alloc(sizeof(field_handlers[0]) *
952-
list->elements);
1199+
field_handlers= (const Type_handler **) thd->alloc(
1200+
sizeof(field_handlers[0]) * list->elements);
9531201
#endif
9541202

955-
for (uint pos= 0; (item=it++); pos++)
956-
{
957-
prot.prepare_for_resend();
958-
if (prot.store_item_metadata(thd, item, pos))
959-
goto err;
960-
if (prot.write())
961-
DBUG_RETURN(1);
1203+
for (uint pos= 0; (item= it++); pos++)
1204+
{
1205+
prot.prepare_for_resend();
1206+
if (prot.store_item_metadata(thd, item, pos))
1207+
goto err;
1208+
if (prot.write())
1209+
DBUG_RETURN(1);
9621210
#ifndef DBUG_OFF
963-
field_handlers[pos]= item->type_handler();
1211+
field_handlers[pos]= item->type_handler();
9641212
#endif
1213+
}
9651214
}
9661215

9671216
if (flags & SEND_EOF)
@@ -1685,7 +1934,8 @@ bool Protocol_binary::send_out_parameters(List<Item_param> *sp_params)
16851934
thd->server_status|= SERVER_PS_OUT_PARAMS | SERVER_MORE_RESULTS_EXISTS;
16861935

16871936
/* Send meta-data. */
1688-
if (send_result_set_metadata(&out_param_lst, SEND_NUM_ROWS | SEND_EOF))
1937+
if (send_result_set_metadata(&out_param_lst,
1938+
SEND_NUM_ROWS | SEND_EOF | SEND_FORCE_COLUMN_INFO))
16891939
return TRUE;
16901940

16911941
/* Send data. */

sql/protocol.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ class Protocol
9393
virtual ~Protocol() {}
9494
void init(THD* thd_arg);
9595

96-
enum { SEND_NUM_ROWS= 1, SEND_EOF= 2 };
96+
enum { SEND_NUM_ROWS= 1, SEND_EOF= 2, SEND_FORCE_COLUMN_INFO= 4 };
9797
virtual bool send_result_set_metadata(List<Item> *list, uint flags);
9898
bool send_list_fields(List<Field> *list, const TABLE_LIST *table_list);
9999
bool send_result_set_row(List<Item> *row_items);

0 commit comments

Comments
 (0)