Skip to content
/ server Public

Commit 28474f4

Browse files
MDEV-36493 Atomic CREATE OR REPLACE ... SELECT blocks InnoDB purge
dict_table_t::parse_tbl_name() : Extracts database and table components from full table names ddl_log_acquire_mdl(): Does take MDL on given table - DELETE/REPLACE: single table MDL acquisition - RENAME: source and destination table MDL protection - ALTER: comprehensive MDL on original, intermediate and final tables - EXCHANGE: primary, source, and temporary table MDL coverage MDL acquistion on temporary tables in mysql_alter_table(), crash recovery for DDL log entry. innodb_mdl_acquire() for temporary tables in commit_inplace_alter_table() innodb_mdl_check() to check MDL ownership verifiction in rename_table() and delete_table(). In innodb_rename_table(), it does check for source and destination table MDL check - InnoDB purge thread now takes MDL on table name directly instead of relying on mdl_name
1 parent 24da629 commit 28474f4

File tree

9 files changed

+359
-30
lines changed

9 files changed

+359
-30
lines changed

sql/ddl_log.cc

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,6 @@ uchar ddl_log_file_magic[]=
8585
{ (uchar) 254, (uchar) 254, (uchar) 11, (uchar) 2 };
8686

8787
/* Action names for ddl_log_action_code */
88-
8988
const char *ddl_log_action_name[DDL_LOG_LAST_ACTION]=
9089
{
9190
"Unknown", "partitioning delete", "partitioning rename",
@@ -96,6 +95,136 @@ const char *ddl_log_action_name[DDL_LOG_LAST_ACTION]=
9695
"delete tmp file", "create trigger", "alter table", "store query"
9796
};
9897

98+
/**
99+
Acquire MDL exclusive lock on a single table name.
100+
@param thd MySQL thread handle
101+
@param db_name Database name
102+
@param tbl_name Table name
103+
*/
104+
static void ddl_log_acquire_table_mdl(
105+
THD *thd, const char *db_name, const char *tbl_name)
106+
{
107+
if (!db_name || !tbl_name || !*db_name || !*tbl_name)
108+
return;
109+
MDL_request request;
110+
MDL_REQUEST_INIT(&request, MDL_key::TABLE, db_name, tbl_name,
111+
MDL_EXCLUSIVE, MDL_TRANSACTION);
112+
113+
thd->mdl_context.acquire_lock(&request, 0);
114+
}
115+
116+
/**
117+
Conditionally acquire MDL based on database name availability.
118+
If db_name is empty, treat table_name as full path and parse it.
119+
Otherwise, use db_name and table_name directly.
120+
@param thd MySQL thread handle
121+
@param db_name Database name (may be empty)
122+
@param table_name Table name or full path
123+
*/
124+
static void ddl_log_acquire_table_mdl(
125+
THD *thd, const LEX_CSTRING &db_name, const LEX_CSTRING &table_name)
126+
{
127+
if (db_name.length == 0)
128+
{
129+
/* If db is empty, treat table_name as full path and parse it */
130+
const char *full_name= table_name.str;
131+
if (!full_name || !*full_name)
132+
return;
133+
134+
/* Skip leading ./ if present */
135+
if (full_name[0] == '.' && full_name[1] == '/')
136+
full_name+= 2;
137+
138+
/* Find database/table separator (either '/' or '.') */
139+
const char *separator= strchr(full_name, '/');
140+
if (!separator)
141+
separator= strchr(full_name, '.');
142+
143+
if (!separator)
144+
return;
145+
146+
const char *db_start= full_name;
147+
const char *tbl_start= separator + 1;
148+
size_t db_len= separator - db_start;
149+
size_t tbl_len= strlen(tbl_start);
150+
151+
if (db_len == 0 || tbl_len == 0 || db_len > NAME_LEN || tbl_len > NAME_LEN)
152+
return;
153+
154+
/* Handle partition table names - look for partition markers like #P# */
155+
const char *part_marker= strstr(tbl_start, "#P#");
156+
if (part_marker)
157+
tbl_len= part_marker - tbl_start;
158+
159+
/* Copy and null-terminate names */
160+
char parsed_db_name[NAME_LEN + 1];
161+
char parsed_tbl_name[NAME_LEN + 1];
162+
memcpy(parsed_db_name, db_start, db_len);
163+
parsed_db_name[db_len]= '\0';
164+
memcpy(parsed_tbl_name, tbl_start, tbl_len);
165+
parsed_tbl_name[tbl_len]= '\0';
166+
167+
ddl_log_acquire_table_mdl(thd, parsed_db_name, parsed_tbl_name);
168+
}
169+
else
170+
{
171+
/* Use db and table names directly */
172+
ddl_log_acquire_table_mdl(thd, db_name.str, table_name.str);
173+
}
174+
}
175+
176+
/**
177+
Acquire MDL exclusive locks for DDL log operations based on action type.
178+
@param thd MySQL thread handle
179+
@param ddl_log_entry DDL log entry containing action type and table names
180+
*/
181+
static void ddl_log_acquire_mdl(THD *thd, DDL_LOG_ENTRY *ddl_log_entry)
182+
{
183+
if (memcmp(ddl_log_entry->handler_name.str, "InnoDB",
184+
ddl_log_entry->handler_name.length))
185+
return;
186+
187+
switch (ddl_log_entry->action_type) {
188+
case DDL_LOG_DELETE_ACTION:
189+
case DDL_LOG_REPLACE_ACTION:
190+
/* Acquire MDL on table being deleted/replaced */
191+
ddl_log_acquire_table_mdl(thd, ddl_log_entry->db, ddl_log_entry->name);
192+
break;
193+
194+
case DDL_LOG_RENAME_ACTION:
195+
case DDL_LOG_RENAME_TABLE_ACTION:
196+
/* Acquire MDL on both source and destination tables for rename */
197+
ddl_log_acquire_table_mdl(thd, ddl_log_entry->from_db,
198+
ddl_log_entry->from_name);
199+
ddl_log_acquire_table_mdl(thd, ddl_log_entry->db, ddl_log_entry->name);
200+
break;
201+
202+
case DDL_LOG_ALTER_TABLE_ACTION:
203+
/* Acquire MDL on all tables involved in alter table operations */
204+
ddl_log_acquire_table_mdl(thd, ddl_log_entry->db, ddl_log_entry->name);
205+
ddl_log_acquire_table_mdl(thd, ddl_log_entry->from_db,
206+
ddl_log_entry->from_name);
207+
ddl_log_acquire_table_mdl(thd, ddl_log_entry->db, ddl_log_entry->extra_name);
208+
break;
209+
210+
case DDL_LOG_EXCHANGE_ACTION:
211+
{
212+
/* Acquire MDL on primary table */
213+
ddl_log_acquire_table_mdl(thd, ddl_log_entry->db, ddl_log_entry->name);
214+
215+
/* Acquire MDL on source table for exchange */
216+
ddl_log_acquire_table_mdl(thd, ddl_log_entry->from_db,
217+
ddl_log_entry->from_name);
218+
219+
ddl_log_acquire_table_mdl(thd, ddl_log_entry->db, ddl_log_entry->tmp_name);
220+
break;
221+
}
222+
default:
223+
/* No MDL acquisition needed for other action types */
224+
break;
225+
}
226+
}
227+
99228
/* Number of phases per entry */
100229
const uchar ddl_log_entry_phases[DDL_LOG_LAST_ACTION]=
101230
{
@@ -1446,6 +1575,10 @@ static int ddl_log_execute_action(THD *thd, MEM_ROOT *mem_root,
14461575
invers_fn_flags|= FN_FROM_IS_TMP;
14471576
}
14481577

1578+
if (!frm_action)
1579+
ddl_log_acquire_mdl(thd, ddl_log_entry);
1580+
1581+
/* Acquire MDL for table operations */
14491582
switch (ddl_log_entry->action_type) {
14501583
case DDL_LOG_REPLACE_ACTION:
14511584
case DDL_LOG_DELETE_ACTION:

sql/sql_partition_admin.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -661,6 +661,14 @@ bool Sql_cmd_alter_table_exchange_partition::
661661
table_list->next_local->db.str,
662662
temp_name, "", FN_IS_TMP);
663663

664+
{
665+
MDL_request mdl_request;
666+
MDL_REQUEST_INIT(&mdl_request, MDL_key::TABLE,
667+
table_list->next_local->db.str,
668+
temp_name, MDL_EXCLUSIVE, MDL_TRANSACTION);
669+
thd->mdl_context.acquire_lock(&mdl_request, 0);
670+
}
671+
664672
if (unlikely(!(part_elem=
665673
part_table->part_info->get_part_elem(partition_name,
666674
part_file_name +

sql/sql_table.cc

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11929,6 +11929,19 @@ do_continue:;
1192911929
*/
1193011930
alter_info->original_table= table;
1193111931

11932+
{
11933+
/*
11934+
Create an exclusive lock on the temporary table name.
11935+
Needed by InnoDB storage engine to avoid MDL on source
11936+
table during purge.
11937+
*/
11938+
MDL_request mdl_request;
11939+
MDL_REQUEST_INIT(&mdl_request, MDL_key::TABLE,
11940+
alter_ctx.new_db.str, alter_ctx.tmp_name.str,
11941+
MDL_EXCLUSIVE, MDL_TRANSACTION);
11942+
thd->mdl_context.acquire_lock(&mdl_request, 0);
11943+
}
11944+
1193211945
/*
1193311946
Create the .frm file for the new table. Storage engine table will not be
1193411947
created at this stage.
@@ -12501,6 +12514,16 @@ do_continue:;
1250112514
If we are changing to use another table handler, we don't
1250212515
have to do the rename as the table names will not interfer.
1250312516
*/
12517+
/*
12518+
Create an exclusive lock on the backup table name.
12519+
Needed by InnoDB storage engine to avoid MDL conflicts
12520+
during backup table operations.
12521+
*/
12522+
MDL_request mdl_request;
12523+
MDL_REQUEST_INIT(&mdl_request, MDL_key::TABLE,
12524+
alter_ctx.db.str, backup_name.str,
12525+
MDL_EXCLUSIVE, MDL_TRANSACTION);
12526+
thd->mdl_context.acquire_lock(&mdl_request, 0);
1250412527
if (mysql_rename_table(old_db_type, &alter_ctx.db, &alter_ctx.table_name,
1250512528
&alter_ctx.db, &backup_name, &alter_ctx.id,
1250612529
FN_TO_IS_TMP |

storage/innobase/dict/dict0dict.cc

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,64 @@ void mdl_release(THD *thd, MDL_ticket *mdl) noexcept
519519
thd->mdl_context.release_lock(mdl);
520520
}
521521

522+
void dict_table_t::parse_tbl_name(
523+
const table_name_t &table_name,
524+
char (&db_name)[NAME_LEN + 1],
525+
char (&tbl_name)[NAME_LEN + 1],
526+
size_t *db_name_len, size_t *tbl_name_len) noexcept
527+
{
528+
char db_buf[MAX_DATABASE_NAME_LEN + 1];
529+
char tbl_buf[MAX_TABLE_NAME_LEN + 1];
530+
531+
const size_t db_len= table_name.dblen();
532+
const char* tbl_start= table_name.basename();
533+
534+
if (db_len == 0 || !tbl_start)
535+
return;
536+
537+
memcpy(db_buf, table_name.m_name, db_len);
538+
db_buf[db_len]= '\0';
539+
540+
size_t tbl_len= strlen(tbl_start);
541+
const bool is_temp= table_name.is_temporary() || table_name.is_create_or_replace();
542+
543+
/* For partition tables, find the base table name by removing partition suffix */
544+
if (const char *is_part = static_cast<const char*>
545+
(memchr(tbl_start, '#', tbl_len)))
546+
{
547+
/* For temporary tables, we need to find the partition marker after the temp name */
548+
if (is_temp)
549+
{
550+
/* Look for partition markers like #P# after the temporary table name */
551+
const char *part_marker= strstr(tbl_start, "#P#");
552+
if (part_marker)
553+
tbl_len= static_cast<size_t>(part_marker - tbl_start);
554+
}
555+
else
556+
{
557+
/* For regular tables, use first # as partition boundary */
558+
tbl_len= static_cast<size_t>(is_part - tbl_start);
559+
}
560+
}
561+
562+
memcpy(tbl_buf, tbl_start, tbl_len);
563+
tbl_buf[tbl_len]= '\0';
564+
565+
/* Convert internal names to SQL names */
566+
*db_name_len= filename_to_tablename(db_buf, db_name,
567+
MAX_DATABASE_NAME_LEN + 1, true);
568+
if (is_temp)
569+
{
570+
memcpy(tbl_name, tbl_buf, tbl_len);
571+
tbl_name[tbl_len]= '\0';
572+
*tbl_name_len= tbl_len;
573+
return;
574+
}
575+
*tbl_name_len= filename_to_tablename(tbl_buf, tbl_name,
576+
MAX_TABLE_NAME_LEN + 1, true);
577+
return;
578+
}
579+
522580
/** Parse the table file name into table name and database name.
523581
@tparam dict_frozen whether the caller holds dict_sys.latch
524582
@param[in,out] db_name database name buffer
@@ -531,47 +589,23 @@ bool dict_table_t::parse_name(char (&db_name)[NAME_LEN + 1],
531589
char (&tbl_name)[NAME_LEN + 1],
532590
size_t *db_name_len, size_t *tbl_name_len) const
533591
{
534-
char db_buf[MAX_DATABASE_NAME_LEN + 1];
535-
char tbl_buf[MAX_TABLE_NAME_LEN + 1];
536-
537592
if (!dict_frozen)
538593
dict_sys.freeze(SRW_LOCK_CALL); /* protect against renaming */
539594
ut_ad(dict_sys.frozen());
540-
const size_t db_len= name.dblen();
541-
ut_ad(db_len <= MAX_DATABASE_NAME_LEN);
542-
543-
memcpy(db_buf, mdl_name.m_name, db_len);
544-
db_buf[db_len]= 0;
545-
546-
size_t tbl_len= strlen(mdl_name.m_name + db_len + 1);
547-
const bool is_temp= mdl_name.is_temporary();
548-
549-
if (is_temp);
550-
else if (const char *is_part= static_cast<const char*>
551-
(memchr(mdl_name.m_name + db_len + 1, '#', tbl_len)))
552-
tbl_len= static_cast<size_t>(is_part - &mdl_name.m_name[db_len + 1]);
553-
554-
memcpy(tbl_buf, mdl_name.m_name + db_len + 1, tbl_len);
555-
tbl_buf[tbl_len]= 0;
556-
595+
parse_tbl_name(name, db_name, tbl_name, db_name_len, tbl_name_len);
557596
if (!dict_frozen)
558597
dict_sys.unfreeze();
559-
560-
*db_name_len= filename_to_tablename(db_buf, db_name,
561-
MAX_DATABASE_NAME_LEN + 1, true);
562-
563-
if (is_temp)
564-
return false;
565-
566-
*tbl_name_len= filename_to_tablename(tbl_buf, tbl_name,
567-
MAX_TABLE_NAME_LEN + 1, true);
568598
return true;
569599
}
570600

571601
template bool
572602
dict_table_t::parse_name<>(char(&)[NAME_LEN + 1], char(&)[NAME_LEN + 1],
573603
size_t*, size_t*) const;
574604

605+
template bool
606+
dict_table_t::parse_name<true>(char(&)[NAME_LEN + 1], char(&)[NAME_LEN + 1],
607+
size_t*, size_t*) const;
608+
575609
dict_table_t *dict_sys_t::acquire_temporary_table(table_id_t id) const noexcept
576610
{
577611
ut_ad(frozen());

0 commit comments

Comments
 (0)