summaryrefslogtreecommitdiff
path: root/innobase/row
diff options
context:
space:
mode:
Diffstat (limited to 'innobase/row')
-rw-r--r--innobase/row/row0ins.c213
-rw-r--r--innobase/row/row0mysql.c180
-rw-r--r--innobase/row/row0purge.c30
-rw-r--r--innobase/row/row0sel.c310
-rw-r--r--innobase/row/row0uins.c6
-rw-r--r--innobase/row/row0undo.c20
-rw-r--r--innobase/row/row0upd.c32
7 files changed, 561 insertions, 230 deletions
diff --git a/innobase/row/row0ins.c b/innobase/row/row0ins.c
index 941c9d5759d..d0a5cfec604 100644
--- a/innobase/row/row0ins.c
+++ b/innobase/row/row0ins.c
@@ -321,59 +321,6 @@ row_ins_clust_index_entry_by_modify(
return(err);
}
-/*******************************************************************
-Checks if a unique key violation to rec would occur at the index entry
-insert. */
-static
-ibool
-row_ins_dupl_error_with_rec(
-/*========================*/
- /* out: TRUE if error */
- rec_t* rec, /* in: user record; NOTE that we assume
- that the caller already has a record lock on
- the record! */
- dtuple_t* entry, /* in: entry to insert */
- dict_index_t* index) /* in: index */
-{
- ulint matched_fields;
- ulint matched_bytes;
- ulint n_unique;
- ulint i;
-
- n_unique = dict_index_get_n_unique(index);
-
- matched_fields = 0;
- matched_bytes = 0;
-
- cmp_dtuple_rec_with_match(entry, rec, &matched_fields, &matched_bytes);
-
- if (matched_fields < n_unique) {
-
- return(FALSE);
- }
-
- /* In a unique secondary index we allow equal key values if they
- contain SQL NULLs */
-
- if (!(index->type & DICT_CLUSTERED)) {
-
- for (i = 0; i < n_unique; i++) {
- if (UNIV_SQL_NULL == dfield_get_len(
- dtuple_get_nth_field(entry, i))) {
-
- return(FALSE);
- }
- }
- }
-
- if (!rec_get_deleted_flag(rec)) {
-
- return(TRUE);
- }
-
- return(FALSE);
-}
-
/*************************************************************************
Either deletes or sets the referencing columns SQL NULL in a child row.
Used in ON DELETE ... clause for foreign keys when a parent row is
@@ -533,8 +480,12 @@ row_ins_foreign_delete_or_set_null(
err = lock_table(0, table, LOCK_IX, thr);
if (err == DB_SUCCESS) {
+ /* Here it suffices to use a LOCK_REC_NOT_GAP type lock;
+ we already have a normal shared lock on the appropriate
+ gap if the search criterion was not unique */
+
err = lock_clust_rec_read_check_and_lock(0, clust_rec,
- clust_index, LOCK_X, thr);
+ clust_index, LOCK_X, LOCK_REC_NOT_GAP, thr);
}
if (err != DB_SUCCESS) {
@@ -630,12 +581,14 @@ nonstandard_exit_func:
/*************************************************************************
Sets a shared lock on a record. Used in locking possible duplicate key
-records. */
+records and also in checking foreign key constraints. */
static
ulint
row_ins_set_shared_rec_lock(
/*========================*/
/* out: DB_SUCCESS or error code */
+ ulint type, /* in: LOCK_ORDINARY, LOCK_GAP, or
+ LOCK_REC_NOT_GAP type lock */
rec_t* rec, /* in: record */
dict_index_t* index, /* in: index */
que_thr_t* thr) /* in: query thread */
@@ -644,10 +597,10 @@ row_ins_set_shared_rec_lock(
if (index->type & DICT_CLUSTERED) {
err = lock_clust_rec_read_check_and_lock(0, rec, index, LOCK_S,
- thr);
+ type, thr);
} else {
err = lock_sec_rec_read_check_and_lock(0, rec, index, LOCK_S,
- thr);
+ type, thr);
}
return(err);
@@ -656,7 +609,7 @@ row_ins_set_shared_rec_lock(
/*******************************************************************
Checks if foreign key constraint fails for an index entry. Sets shared locks
which lock either the success or the failure of the constraint. NOTE that
-the caller must have a shared latch on dict_foreign_key_check_lock. */
+the caller must have a shared latch on dict_operation_lock. */
ulint
row_ins_check_foreign_constraint(
@@ -679,7 +632,7 @@ row_ins_check_foreign_constraint(
dict_table_t* check_table;
dict_index_t* check_index;
ulint n_fields_cmp;
- ibool timeout_expired;
+ ibool unique_search;
rec_t* rec;
btr_pcur_t pcur;
ibool moved;
@@ -689,7 +642,9 @@ row_ins_check_foreign_constraint(
mtr_t mtr;
run_again:
- ut_ad(rw_lock_own(&dict_foreign_key_check_lock, RW_LOCK_SHARED));
+ ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_SHARED));
+
+ err = DB_SUCCESS;
if (thr_get_trx(thr)->check_foreigns == FALSE) {
/* The user has suppressed foreign key checks currently for
@@ -748,6 +703,14 @@ run_again:
dtuple_set_n_fields_cmp(entry, foreign->n_fields);
+ if (dict_index_get_n_unique(check_index) <= foreign->n_fields) {
+ /* We can just set a LOCK_REC_NOT_GAP type lock */
+
+ unique_search = TRUE;
+ } else {
+ unique_search = FALSE;
+ }
+
btr_pcur_open(check_index, entry, PAGE_CUR_GE,
BTR_SEARCH_LEAF, &pcur, &mtr);
@@ -761,25 +724,45 @@ run_again:
goto next_rec;
}
- /* Try to place a lock on the index record */
-
- err = row_ins_set_shared_rec_lock(rec, check_index, thr);
-
- if (err != DB_SUCCESS) {
-
- break;
- }
-
if (rec == page_get_supremum_rec(buf_frame_align(rec))) {
+ err = row_ins_set_shared_rec_lock(LOCK_ORDINARY, rec,
+ check_index, thr);
+ if (err != DB_SUCCESS) {
+
+ break;
+ }
+
goto next_rec;
}
cmp = cmp_dtuple_rec(entry, rec);
if (cmp == 0) {
- if (!rec_get_deleted_flag(rec)) {
+ if (rec_get_deleted_flag(rec)) {
+ err = row_ins_set_shared_rec_lock(LOCK_ORDINARY,
+ rec, check_index, thr);
+ if (err != DB_SUCCESS) {
+
+ break;
+ }
+ } else {
/* Found a matching record */
+
+ if (unique_search) {
+ err = row_ins_set_shared_rec_lock(
+ LOCK_REC_NOT_GAP,
+ rec, check_index, thr);
+ } else {
+ err = row_ins_set_shared_rec_lock(
+ LOCK_ORDINARY,
+ rec, check_index, thr);
+ }
+
+ if (err != DB_SUCCESS) {
+
+ break;
+ }
/* printf(
"FOREIGN: Found matching record from %s %s\n",
@@ -807,6 +790,13 @@ run_again:
}
if (cmp < 0) {
+ err = row_ins_set_shared_rec_lock(LOCK_GAP,
+ rec, check_index, thr);
+ if (err != DB_SUCCESS) {
+
+ break;
+ }
+
if (check_ref) {
err = DB_NO_REFERENCED_ROW;
} else {
@@ -844,14 +834,14 @@ do_possible_lock_wait:
que_thr_stop_for_mysql(thr);
- timeout_expired = srv_suspend_mysql_thread(thr);
+ srv_suspend_mysql_thread(thr);
- if (!timeout_expired) {
+ if (thr_get_trx(thr)->error_state == DB_SUCCESS) {
goto run_again;
}
- err = DB_LOCK_WAIT_TIMEOUT;
+ err = thr_get_trx(thr)->error_state;
}
return(err);
@@ -890,21 +880,16 @@ row_ins_check_foreign_constraints(
trx);
}
- if (!trx->has_dict_foreign_key_check_lock) {
+ if (0 == trx->dict_operation_lock_mode) {
got_s_lock = TRUE;
- rw_lock_s_lock(&dict_foreign_key_check_lock);
-
- trx->has_dict_foreign_key_check_lock = TRUE;
+ row_mysql_freeze_data_dictionary(trx);
}
err = row_ins_check_foreign_constraint(TRUE, foreign,
table, index, entry, thr);
if (got_s_lock) {
-
- rw_lock_s_unlock(&dict_foreign_key_check_lock);
-
- trx->has_dict_foreign_key_check_lock = FALSE;
+ row_mysql_unfreeze_data_dictionary(trx);
}
if (err != DB_SUCCESS) {
@@ -919,6 +904,59 @@ row_ins_check_foreign_constraints(
}
/*******************************************************************
+Checks if a unique key violation to rec would occur at the index entry
+insert. */
+static
+ibool
+row_ins_dupl_error_with_rec(
+/*========================*/
+ /* out: TRUE if error */
+ rec_t* rec, /* in: user record; NOTE that we assume
+ that the caller already has a record lock on
+ the record! */
+ dtuple_t* entry, /* in: entry to insert */
+ dict_index_t* index) /* in: index */
+{
+ ulint matched_fields;
+ ulint matched_bytes;
+ ulint n_unique;
+ ulint i;
+
+ n_unique = dict_index_get_n_unique(index);
+
+ matched_fields = 0;
+ matched_bytes = 0;
+
+ cmp_dtuple_rec_with_match(entry, rec, &matched_fields, &matched_bytes);
+
+ if (matched_fields < n_unique) {
+
+ return(FALSE);
+ }
+
+ /* In a unique secondary index we allow equal key values if they
+ contain SQL NULLs */
+
+ if (!(index->type & DICT_CLUSTERED)) {
+
+ for (i = 0; i < n_unique; i++) {
+ if (UNIV_SQL_NULL == dfield_get_len(
+ dtuple_get_nth_field(entry, i))) {
+
+ return(FALSE);
+ }
+ }
+ }
+
+ if (!rec_get_deleted_flag(rec)) {
+
+ return(TRUE);
+ }
+
+ return(FALSE);
+}
+
+/*******************************************************************
Scans a unique non-clustered index at a given index entry to determine
whether a uniqueness violation has occurred for the key value of the entry.
Set shared locks on possible duplicate records. */
@@ -976,9 +1014,10 @@ row_ins_scan_sec_index_for_duplicate(
goto next_rec;
}
- /* Try to place a lock on the index record */
+ /* Try to place a lock on the index record */
- err = row_ins_set_shared_rec_lock(rec, index, thr);
+ err = row_ins_set_shared_rec_lock(LOCK_ORDINARY, rec, index,
+ thr);
if (err != DB_SUCCESS) {
@@ -1082,8 +1121,8 @@ row_ins_duplicate_error_in_clust(
sure that in roll-forward we get the same duplicate
errors as in original execution */
- err = row_ins_set_shared_rec_lock(rec, cursor->index,
- thr);
+ err = row_ins_set_shared_rec_lock(LOCK_REC_NOT_GAP,
+ rec, cursor->index, thr);
if (err != DB_SUCCESS) {
return(err);
@@ -1105,8 +1144,8 @@ row_ins_duplicate_error_in_clust(
if (rec != page_get_supremum_rec(page)) {
- err = row_ins_set_shared_rec_lock(rec, cursor->index,
- thr);
+ err = row_ins_set_shared_rec_lock(LOCK_REC_NOT_GAP,
+ rec, cursor->index, thr);
if (err != DB_SUCCESS) {
return(err);
diff --git a/innobase/row/row0mysql.c b/innobase/row/row0mysql.c
index cea8f1316fe..9ce86b5d487 100644
--- a/innobase/row/row0mysql.c
+++ b/innobase/row/row0mysql.c
@@ -27,6 +27,7 @@ Created 9/17/2000 Heikki Tuuri
#include "lock0lock.h"
#include "rem0cmp.h"
#include "log0log.h"
+#include "btr0sea.h"
/* A dummy variable used to fool the compiler */
ibool row_mysql_identically_false = FALSE;
@@ -197,13 +198,13 @@ row_mysql_handle_errors(
/* out: TRUE if it was a lock wait and
we should continue running the query thread */
ulint* new_err,/* out: possible new error encountered in
- rollback, or the old error which was
- during the function entry */
+ lock wait, or if no new error, the value
+ of trx->error_state at the entry of this
+ function */
trx_t* trx, /* in: transaction */
que_thr_t* thr, /* in: query thread */
trx_savept_t* savept) /* in: savepoint or NULL */
{
- ibool timeout_expired;
ulint err;
handle_new_error:
@@ -240,11 +241,9 @@ handle_new_error:
/* MySQL will roll back the latest SQL statement */
} else if (err == DB_LOCK_WAIT) {
- timeout_expired = srv_suspend_mysql_thread(thr);
-
- if (timeout_expired) {
- trx->error_state = DB_LOCK_WAIT_TIMEOUT;
+ srv_suspend_mysql_thread(thr);
+ if (trx->error_state != DB_SUCCESS) {
que_thr_stop_for_mysql(thr);
goto handle_new_error;
@@ -321,6 +320,8 @@ row_create_prebuilt(
prebuilt->sql_stat_start = TRUE;
+ prebuilt->mysql_has_locked = FALSE;
+
prebuilt->index = NULL;
prebuilt->n_template = 0;
prebuilt->mysql_template = NULL;
@@ -1000,8 +1001,8 @@ row_update_cascade_for_mysql(
or set null operation */
dict_table_t* table) /* in: table where we do the operation */
{
- ulint err;
- trx_t* trx;
+ ulint err;
+ trx_t* trx;
trx = thr_get_trx(thr);
run_again:
@@ -1012,11 +1013,28 @@ run_again:
err = trx->error_state;
+ /* Note that the cascade node is a subnode of another InnoDB
+ query graph node. We do a normal lock wait in this node, but
+ all errors are handled by the parent node. */
+
if (err == DB_LOCK_WAIT) {
- que_thr_stop_for_mysql(thr);
+ /* Handle lock wait here */
- row_mysql_handle_errors(&err, trx, thr, NULL);
+ que_thr_stop_for_mysql(thr);
+
+ srv_suspend_mysql_thread(thr);
+
+ /* Note that a lock wait may also end in a lock wait timeout,
+ or this transaction is picked as a victim in selective
+ deadlock resolution */
+
+ if (trx->error_state != DB_SUCCESS) {
+ return(trx->error_state);
+ }
+
+ /* Retry operation after a normal lock wait */
+
goto run_again;
}
@@ -1136,32 +1154,73 @@ row_mysql_recover_tmp_table(
}
/*************************************************************************
-Locks the data dictionary exclusively for performing a table create
-operation. */
+Locks the data dictionary in shared mode from modifications, for performing
+foreign key check, rollback, or other operation invisible to MySQL. */
+
+void
+row_mysql_freeze_data_dictionary(
+/*=============================*/
+ trx_t* trx) /* in: transaction */
+{
+ ut_a(trx->dict_operation_lock_mode == 0);
+
+ rw_lock_s_lock(&dict_operation_lock);
+
+ trx->dict_operation_lock_mode = RW_S_LATCH;
+}
+
+/*************************************************************************
+Unlocks the data dictionary shared lock. */
+
+void
+row_mysql_unfreeze_data_dictionary(
+/*===============================*/
+ trx_t* trx) /* in: transaction */
+{
+ ut_a(trx->dict_operation_lock_mode == RW_S_LATCH);
+
+ rw_lock_s_unlock(&dict_operation_lock);
+
+ trx->dict_operation_lock_mode = 0;
+}
+
+/*************************************************************************
+Locks the data dictionary exclusively for performing a table create or other
+data dictionary modification operation. */
void
-row_mysql_lock_data_dictionary(void)
-/*================================*/
+row_mysql_lock_data_dictionary(
+/*===========================*/
+ trx_t* trx) /* in: transaction */
{
+ ut_a(trx->dict_operation_lock_mode == 0);
+
/* Serialize data dictionary operations with dictionary mutex:
no deadlocks or lock waits can occur then in these operations */
- rw_lock_x_lock(&(dict_foreign_key_check_lock));
+ rw_lock_x_lock(&dict_operation_lock);
+ trx->dict_operation_lock_mode = RW_X_LATCH;
+
mutex_enter(&(dict_sys->mutex));
}
/*************************************************************************
-Unlocks the data dictionary exclusively lock. */
+Unlocks the data dictionary exclusive lock. */
void
-row_mysql_unlock_data_dictionary(void)
-/*==================================*/
+row_mysql_unlock_data_dictionary(
+/*=============================*/
+ trx_t* trx) /* in: transaction */
{
+ ut_a(trx->dict_operation_lock_mode == RW_X_LATCH);
+
/* Serialize data dictionary operations with dictionary mutex:
no deadlocks can occur then in these operations */
mutex_exit(&(dict_sys->mutex));
- rw_lock_x_unlock(&(dict_foreign_key_check_lock));
+ rw_lock_x_unlock(&dict_operation_lock);
+
+ trx->dict_operation_lock_mode = 0;
}
/*************************************************************************
@@ -1184,6 +1243,8 @@ row_create_table_for_mysql(
ulint err;
ut_ad(trx->mysql_thread_id == os_thread_get_curr_id());
+ ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
+ ut_ad(trx->dict_operation_lock_mode == RW_X_LATCH);
ut_ad(mutex_own(&(dict_sys->mutex)));
if (srv_created_new_raw) {
@@ -1332,7 +1393,7 @@ row_create_table_for_mysql(
fprintf(stderr,
"InnoDB: Warning: cannot create table %s because tablespace full\n",
table->name);
- row_drop_table_for_mysql(table->name, trx, TRUE);
+ row_drop_table_for_mysql(table->name, trx);
} else {
ut_a(err == DB_DUPLICATE_KEY);
@@ -1383,7 +1444,8 @@ row_create_index_for_mysql(
ulint namelen;
ulint keywordlen;
ulint err;
-
+
+ ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
ut_ad(mutex_own(&(dict_sys->mutex)));
ut_ad(trx->mysql_thread_id == os_thread_get_curr_id());
@@ -1425,7 +1487,7 @@ row_create_index_for_mysql(
trx_general_rollback_for_mysql(trx, FALSE, NULL);
- row_drop_table_for_mysql(index->table_name, trx, TRUE);
+ row_drop_table_for_mysql(index->table_name, trx);
trx->error_state = DB_SUCCESS;
}
@@ -1464,6 +1526,7 @@ row_table_add_foreign_constraints(
ulint err;
ut_ad(mutex_own(&(dict_sys->mutex)));
+ ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
ut_a(sql_string);
trx->op_info = (char *) "adding foreign keys";
@@ -1498,7 +1561,7 @@ row_table_add_foreign_constraints(
trx_general_rollback_for_mysql(trx, FALSE, NULL);
- row_drop_table_for_mysql(name, trx, TRUE);
+ row_drop_table_for_mysql(name, trx);
trx->error_state = DB_SUCCESS;
}
@@ -1529,7 +1592,7 @@ row_drop_table_for_mysql_in_background(
name); */
/* Drop the table in InnoDB */
- error = row_drop_table_for_mysql(name, trx, FALSE);
+ error = row_drop_table_for_mysql(name, trx);
if (error != DB_SUCCESS) {
fprintf(stderr,
@@ -1688,9 +1751,7 @@ row_drop_table_for_mysql(
/*=====================*/
/* out: error code or DB_SUCCESS */
char* name, /* in: table name */
- trx_t* trx, /* in: transaction handle */
- ibool has_dict_mutex) /* in: TRUE if the caller already owns the
- dictionary system mutex */
+ trx_t* trx) /* in: transaction handle */
{
dict_table_t* table;
que_thr_t* thr;
@@ -1702,6 +1763,7 @@ row_drop_table_for_mysql(
ulint namelen;
ulint keywordlen;
ulint rounds = 0;
+ ibool locked_dictionary = FALSE;
char buf[10000];
ut_ad(trx->mysql_thread_id == os_thread_get_curr_id());
@@ -1845,13 +1907,18 @@ row_drop_table_for_mysql(
/* Serialize data dictionary operations with dictionary mutex:
no deadlocks can occur then in these operations */
- if (!has_dict_mutex) {
- /* Prevent foreign key checks while we are dropping the table */
- rw_lock_x_lock(&(dict_foreign_key_check_lock));
+ if (trx->dict_operation_lock_mode != RW_X_LATCH) {
+ /* Prevent foreign key checks etc. while we are dropping the
+ table */
+
+ row_mysql_lock_data_dictionary(trx);
- mutex_enter(&(dict_sys->mutex));
+ locked_dictionary = TRUE;
}
+ ut_ad(mutex_own(&(dict_sys->mutex)));
+ ut_ad(rw_lock_own(&dict_operation_lock, RW_LOCK_EX));
+
graph = pars_sql(buf);
ut_a(graph);
@@ -1861,9 +1928,6 @@ row_drop_table_for_mysql(
graph->fork_type = QUE_FORK_MYSQL_INTERFACE;
- /* Prevent purge from running while we are dropping the table */
- rw_lock_s_lock(&(purge_sys->purge_is_running));
-
table = dict_table_get_low(name);
if (!table) {
@@ -1944,12 +2008,10 @@ row_drop_table_for_mysql(
}
}
-funct_exit:
- rw_lock_s_unlock(&(purge_sys->purge_is_running));
+funct_exit:
- if (!has_dict_mutex) {
- mutex_exit(&(dict_sys->mutex));
- rw_lock_x_unlock(&(dict_foreign_key_check_lock));
+ if (locked_dictionary) {
+ row_mysql_unlock_data_dictionary(trx);
}
que_graph_free(graph);
@@ -1985,8 +2047,7 @@ row_drop_database_for_mysql(
trx_start_if_not_started(trx);
loop:
- rw_lock_x_lock(&(dict_foreign_key_check_lock));
- mutex_enter(&(dict_sys->mutex));
+ row_mysql_lock_data_dictionary(trx);
while ((table_name = dict_get_first_table_name_in_db(name))) {
ut_a(memcmp(table_name, name, strlen(name)) == 0);
@@ -1999,8 +2060,7 @@ loop:
the table */
if (table->n_mysql_handles_opened > 0) {
- mutex_exit(&(dict_sys->mutex));
- rw_lock_x_unlock(&(dict_foreign_key_check_lock));
+ row_mysql_unlock_data_dictionary(trx);
ut_print_timestamp(stderr);
fprintf(stderr,
@@ -2015,7 +2075,7 @@ loop:
goto loop;
}
- err = row_drop_table_for_mysql(table_name, trx, TRUE);
+ err = row_drop_table_for_mysql(table_name, trx);
mem_free(table_name);
@@ -2027,8 +2087,7 @@ loop:
}
}
- mutex_exit(&(dict_sys->mutex));
- rw_lock_x_unlock(&(dict_foreign_key_check_lock));
+ row_mysql_unlock_data_dictionary(trx);
trx_commit_for_mysql(trx);
@@ -2165,8 +2224,7 @@ row_rename_table_for_mysql(
/* Serialize data dictionary operations with dictionary mutex:
no deadlocks can occur then in these operations */
- rw_lock_x_lock(&(dict_foreign_key_check_lock));
- mutex_enter(&(dict_sys->mutex));
+ row_mysql_lock_data_dictionary(trx);
table = dict_table_get_low(old_name);
@@ -2248,8 +2306,7 @@ row_rename_table_for_mysql(
}
}
funct_exit:
- mutex_exit(&(dict_sys->mutex));
- rw_lock_x_unlock(&(dict_foreign_key_check_lock));
+ row_mysql_unlock_data_dictionary(trx);
que_graph_free(graph);
@@ -2394,18 +2451,28 @@ row_check_table_for_mysql(
row_prebuilt_t* prebuilt) /* in: prebuilt struct in MySQL
handle */
{
- dict_table_t* table = prebuilt->table;
+ dict_table_t* table = prebuilt->table;
dict_index_t* index;
ulint n_rows;
ulint n_rows_in_table = ULINT_UNDEFINED;
- ulint ret = DB_SUCCESS;
-
+ ulint ret = DB_SUCCESS;
+ ulint old_isolation_level;
+
prebuilt->trx->op_info = (char *) "checking table";
+ old_isolation_level = prebuilt->trx->isolation_level;
+
+ /* We must run the index record counts at an isolation level
+ >= READ COMMITTED, because a dirty read can see a wrong number
+ of records in some index; to play safe, we use always
+ REPEATABLE READ here */
+
+ prebuilt->trx->isolation_level = TRX_ISO_REPEATABLE_READ;
+
index = dict_table_get_first_index(table);
while (index != NULL) {
- /* fprintf(stderr, "Validating index %s\n", index->name); */
+ /* fprintf(stderr, "Validating index %s\n", index->name); */
if (!btr_validate_tree(index->tree)) {
ret = DB_ERROR;
@@ -2433,6 +2500,9 @@ row_check_table_for_mysql(
index = dict_table_get_next_index(index);
}
+ /* Restore the original isolation level */
+ prebuilt->trx->isolation_level = old_isolation_level;
+
/* We validate also the whole adaptive hash index for all tables
at every CHECK TABLE */
diff --git a/innobase/row/row0purge.c b/innobase/row/row0purge.c
index 60e057b816e..b64003f22d4 100644
--- a/innobase/row/row0purge.c
+++ b/innobase/row/row0purge.c
@@ -24,6 +24,7 @@ Created 3/14/1997 Heikki Tuuri
#include "row0row.h"
#include "row0upd.h"
#include "row0vers.h"
+#include "row0mysql.h"
#include "log0log.h"
/************************************************************************
@@ -453,7 +454,9 @@ static
ibool
row_purge_parse_undo_rec(
/*=====================*/
- /* out: TRUE if purge operation required */
+ /* out: TRUE if purge operation required:
+ NOTE that then the CALLER must unfreeze
+ data dictionary! */
purge_node_t* node, /* in: row undo node */
ibool* updated_extern,
/* out: TRUE if an externally stored field
@@ -462,6 +465,7 @@ row_purge_parse_undo_rec(
{
dict_index_t* clust_index;
byte* ptr;
+ trx_t* trx;
dulint undo_no;
dulint table_id;
dulint trx_id;
@@ -471,6 +475,8 @@ row_purge_parse_undo_rec(
ulint cmpl_info;
ut_ad(node && thr);
+
+ trx = thr_get_trx(thr);
ptr = trx_undo_rec_get_pars(node->undo_rec, &type, &cmpl_info,
updated_extern, &undo_no, &table_id);
@@ -493,18 +499,21 @@ row_purge_parse_undo_rec(
return(FALSE);
}
- mutex_enter(&(dict_sys->mutex));
+ /* Prevent DROP TABLE etc. from running when we are doing the purge
+ for this row */
- node->table = dict_table_get_on_id_low(table_id, thr_get_trx(thr));
+ row_mysql_freeze_data_dictionary(trx);
- rw_lock_x_lock(&(purge_sys->purge_is_running));
+ mutex_enter(&(dict_sys->mutex));
- mutex_exit(&(dict_sys->mutex));
+ node->table = dict_table_get_on_id_low(table_id, thr_get_trx(thr));
+ mutex_exit(&(dict_sys->mutex));
+
if (node->table == NULL) {
/* The table has been dropped: no need to do purge */
- rw_lock_x_unlock(&(purge_sys->purge_is_running));
+ row_mysql_unfreeze_data_dictionary(trx);
return(FALSE);
}
@@ -514,7 +523,7 @@ row_purge_parse_undo_rec(
if (clust_index == NULL) {
/* The table was corrupt in the data dictionary */
- rw_lock_x_unlock(&(purge_sys->purge_is_running));
+ row_mysql_unfreeze_data_dictionary(trx);
return(FALSE);
}
@@ -552,9 +561,12 @@ row_purge(
dulint roll_ptr;
ibool purge_needed;
ibool updated_extern;
+ trx_t* trx;
ut_ad(node && thr);
+ trx = thr_get_trx(thr);
+
node->undo_rec = trx_purge_fetch_next_rec(&roll_ptr,
&(node->reservation),
node->heap);
@@ -573,6 +585,8 @@ row_purge(
} else {
purge_needed = row_purge_parse_undo_rec(node, &updated_extern,
thr);
+ /* If purge_needed == TRUE, we must also remember to unfreeze
+ data dictionary! */
}
if (purge_needed) {
@@ -594,7 +608,7 @@ row_purge(
btr_pcur_close(&(node->pcur));
}
- rw_lock_x_unlock(&(purge_sys->purge_is_running));
+ row_mysql_unfreeze_data_dictionary(trx);
}
/* Do some cleanup */
diff --git a/innobase/row/row0sel.c b/innobase/row/row0sel.c
index 4af04251996..ce6ed091a48 100644
--- a/innobase/row/row0sel.c
+++ b/innobase/row/row0sel.c
@@ -606,7 +606,7 @@ row_sel_get_clust_rec(
/* Try to place a lock on the index record */
err = lock_clust_rec_read_check_and_lock(0, clust_rec, index,
- node->row_lock_mode, thr);
+ node->row_lock_mode, LOCK_ORDINARY, thr);
if (err != DB_SUCCESS) {
return(err);
@@ -621,7 +621,7 @@ row_sel_get_clust_rec(
node->read_view)) {
err = row_sel_build_prev_vers(node->read_view, plan,
- clust_rec, &old_vers, mtr);
+ clust_rec, &old_vers, mtr);
if (err != DB_SUCCESS) {
return(err);
@@ -678,16 +678,17 @@ sel_set_rec_lock(
rec_t* rec, /* in: record */
dict_index_t* index, /* in: index */
ulint mode, /* in: lock mode */
+ ulint type, /* in: LOCK_ORDINARY, LOCK_GAP, or LOC_REC_NOT_GAP */
que_thr_t* thr) /* in: query thread */
{
ulint err;
if (index->type & DICT_CLUSTERED) {
err = lock_clust_rec_read_check_and_lock(0, rec, index, mode,
- thr);
+ type, thr);
} else {
err = lock_sec_rec_read_check_and_lock(0, rec, index, mode,
- thr);
+ type, thr);
}
return(err);
@@ -1154,7 +1155,7 @@ rec_loop:
if (!consistent_read) {
err = sel_set_rec_lock(page_rec_get_next(rec), index,
- node->row_lock_mode, thr);
+ node->row_lock_mode, LOCK_ORDINARY, thr);
if (err != DB_SUCCESS) {
/* Note that in this case we will store in pcur
the PREDECESSOR of the record we are waiting
@@ -1180,8 +1181,8 @@ rec_loop:
if (!consistent_read) {
/* Try to place a lock on the index record */
- err = sel_set_rec_lock(rec, index, node->row_lock_mode, thr);
-
+ err = sel_set_rec_lock(rec, index, node->row_lock_mode,
+ LOCK_ORDINARY, thr);
if (err != DB_SUCCESS) {
goto lock_wait_or_error;
@@ -2200,6 +2201,7 @@ row_sel_get_clust_rec_for_mysql(
rec_t* old_vers;
ulint err;
trx_t* trx;
+ char err_buf[1000];
*out_rec = NULL;
@@ -2213,14 +2215,41 @@ row_sel_get_clust_rec_for_mysql(
clust_rec = btr_pcur_get_rec(prebuilt->clust_pcur);
- ut_ad(page_rec_is_user_rec(clust_rec));
+ if (!page_rec_is_user_rec(clust_rec)) {
+ ut_print_timestamp(stderr);
+ fprintf(stderr,
+ " InnoDB: error clustered record for sec rec not found\n"
+ "InnoDB: index %s table %s\n", sec_index->name,
+ sec_index->table->name);
+
+ rec_sprintf(err_buf, 900, rec);
+ fprintf(stderr, "InnoDB: sec index record %s\n", err_buf);
+
+ rec_sprintf(err_buf, 900, clust_rec);
+ fprintf(stderr, "InnoDB: clust index record %s\n", err_buf);
+
+ trx = thr_get_trx(thr);
+ trx_print(err_buf, trx);
+
+ fprintf(stderr,
+ "%s\nInnoDB: Make a detailed bug report and send it\n",
+ err_buf);
+ fprintf(stderr, "InnoDB: to mysql@lists.mysql.com\n");
+
+ clust_rec = NULL;
+
+ goto func_exit;
+ }
if (prebuilt->select_lock_type != LOCK_NONE) {
- /* Try to place a lock on the index record */
+ /* Try to place a lock on the index record; we are searching
+ the clust rec with a unique condition, hence
+ we set a LOCK_REC_NOT_GAP type lock */
err = lock_clust_rec_read_check_and_lock(0, clust_rec,
clust_index,
- prebuilt->select_lock_type, thr);
+ prebuilt->select_lock_type,
+ LOCK_REC_NOT_GAP, thr);
if (err != DB_SUCCESS) {
return(err);
@@ -2232,8 +2261,12 @@ row_sel_get_clust_rec_for_mysql(
trx = thr_get_trx(thr);
old_vers = NULL;
-
- if (!lock_clust_rec_cons_read_sees(clust_rec, clust_index,
+
+ /* If the isolation level allows reading of uncommitted data,
+ then we never look for an earlier version */
+
+ if (trx->isolation_level > TRX_ISO_READ_UNCOMMITTED
+ && !lock_clust_rec_cons_read_sees(clust_rec, clust_index,
trx->read_view)) {
err = row_sel_build_prev_vers_for_mysql(
@@ -2275,6 +2308,7 @@ row_sel_get_clust_rec_for_mysql(
}
}
+func_exit:
*out_rec = clust_rec;
if (prebuilt->select_lock_type == LOCK_X) {
@@ -2407,7 +2441,7 @@ row_sel_push_cache_row_for_mysql(
/*************************************************************************
Tries to do a shortcut to fetch a clustered index record with a unique key,
using the hash index if possible (not always). We assume that the search
-mode is PAGE_CUR_GE, it is a consistent read, trx has already a read view,
+mode is PAGE_CUR_GE, it is a consistent read, there is a read view in trx,
btr search latch has been locked in S-mode. */
static
ulint
@@ -2426,7 +2460,7 @@ row_sel_try_search_shortcut_for_mysql(
ut_ad(index->type & DICT_CLUSTERED);
ut_ad(!prebuilt->templ_contains_blob);
-
+
btr_pcur_open_with_no_init(index, search_tuple, PAGE_CUR_GE,
BTR_SEARCH_LEAF, pcur,
#ifndef UNIV_SEARCH_DEBUG
@@ -2516,17 +2550,23 @@ row_search_for_mysql(
ibool was_lock_wait;
ulint ret;
ulint shortcut;
+ ibool unique_search = FALSE;
ibool unique_search_from_clust_index = FALSE;
ibool mtr_has_extra_clust_latch = FALSE;
ibool moves_up = FALSE;
+ ibool set_also_gap_locks = TRUE;
+ /* if the query is a plain
+ locking SELECT, and the isolation
+ level is <= TRX_ISO_READ_COMMITTED,
+ then this is set to FALSE */
+ ibool success;
ulint cnt = 0;
+ ulint next_offs;
mtr_t mtr;
ut_ad(index && pcur && search_tuple);
ut_ad(trx->mysql_thread_id == os_thread_get_curr_id());
-
- ut_ad(sync_thread_levels_empty_gen(FALSE));
-
+
if (prebuilt->magic_n != ROW_PREBUILT_ALLOCATED) {
fprintf(stderr,
"InnoDB: Error: trying to free a corrupt\n"
@@ -2543,6 +2583,9 @@ row_search_for_mysql(
printf("N tables locked %lu\n", trx->mysql_n_tables_locked);
*/
+ /*-------------------------------------------------------------*/
+ /* PHASE 1: Try to pop the row from the prefetch cache */
+
if (direction == 0) {
trx->op_info = (char *) "starting index read";
@@ -2608,18 +2651,35 @@ row_search_for_mysql(
mtr_start(&mtr);
- /* Since we must release the search system latch when we retrieve an
- externally stored field, we cannot use the adaptive hash index in a
- search in the case the row may be long and there may be externally
- stored fields */
+ /* In a search where at most one record in the index may match, we
+ can use a LOCK_REC_NOT_GAP type record lock when locking a non-delete
+ marked matching record.
+
+ Note that in a unique secondary index there may be different delete
+ marked versions of a record where only the primary key values differ:
+ thus in a secondary index we must use next-key locks when locking
+ delete marked records. */
if (match_mode == ROW_SEL_EXACT
- && index->type & DICT_UNIQUE
- && index->type & DICT_CLUSTERED
- && !prebuilt->templ_contains_blob
- && (prebuilt->mysql_row_len < UNIV_PAGE_SIZE / 8)
- && dtuple_get_n_fields(search_tuple)
+ && index->type & DICT_UNIQUE
+ && dtuple_get_n_fields(search_tuple)
== dict_index_get_n_unique(index)) {
+ unique_search = TRUE;
+ }
+
+ /*-------------------------------------------------------------*/
+ /* PHASE 2: Try fast adaptive hash index search if possible */
+
+ /* Next test if this is the special case where we can use the fast
+ adaptive hash index to try the search. Since we must release the
+ search system latch when we retrieve an externally stored field, we
+ cannot use the adaptive hash index in a search in the case the row
+ may be long and there may be externally stored fields */
+
+ if (unique_search
+ && index->type & DICT_CLUSTERED
+ && !prebuilt->templ_contains_blob
+ && (prebuilt->mysql_row_len < UNIV_PAGE_SIZE / 8)) {
if (direction == ROW_SEL_NEXT) {
/* MySQL sometimes seems to do fetch next even
@@ -2642,8 +2702,9 @@ row_search_for_mysql(
unique_search_from_clust_index = TRUE;
- if (trx->mysql_n_tables_locked == 0
- && !prebuilt->sql_stat_start) {
+ if (prebuilt->select_lock_type == LOCK_NONE
+ && trx->isolation_level > TRX_ISO_READ_UNCOMMITTED
+ && trx->read_view) {
/* This is a SELECT query done as a consistent read,
and the read view has already been allocated:
@@ -2722,13 +2783,34 @@ row_search_for_mysql(
mtr_start(&mtr);
}
}
-no_shortcut:
+
+no_shortcut:
+ /*-------------------------------------------------------------*/
+ /* PHASE 3: Open or restore index cursor position */
+
if (trx->has_search_latch) {
rw_lock_s_unlock(&btr_search_latch);
trx->has_search_latch = FALSE;
}
trx_start_if_not_started(trx);
+
+ if (trx->isolation_level <= TRX_ISO_READ_COMMITTED
+ && prebuilt->select_lock_type != LOCK_NONE
+ && trx->mysql_query_str) {
+
+ /* Scan the MySQL query string; check if SELECT is the first
+ word there */
+
+ dict_accept(*trx->mysql_query_str, "SELECT", &success);
+
+ if (success) {
+ /* It is a plain locking SELECT and the isolation
+ level is low: do not lock gaps */
+
+ set_also_gap_locks = FALSE;
+ }
+ }
/* Note that if the search mode was GE or G, then the cursor
naturally moves upward (in fetch next) in alphabetical order,
@@ -2793,8 +2875,10 @@ no_shortcut:
prebuilt->sql_stat_start = FALSE;
}
- /*-------------------------------------------------------------*/
rec_loop:
+ /*-------------------------------------------------------------*/
+ /* PHASE 4: Look for matching records in a loop */
+
cons_read_requires_clust_rec = FALSE;
rec = btr_pcur_get_rec(pcur);
@@ -2812,37 +2896,88 @@ rec_loop:
goto next_rec;
}
-
- if (prebuilt->select_lock_type != LOCK_NONE) {
- /* Try to place a lock on the index record */
- err = sel_set_rec_lock(rec, index, prebuilt->select_lock_type,
- thr);
- if (err != DB_SUCCESS) {
+ if (rec == page_get_supremum_rec(buf_frame_align(rec))) {
- goto lock_wait_or_error;
- }
- }
+ if (prebuilt->select_lock_type != LOCK_NONE
+ && set_also_gap_locks) {
- if (rec == page_get_supremum_rec(buf_frame_align(rec))) {
+ /* Try to place a lock on the index record */
+
+ err = sel_set_rec_lock(rec, index,
+ prebuilt->select_lock_type,
+ LOCK_ORDINARY, thr);
+ if (err != DB_SUCCESS) {
+ goto lock_wait_or_error;
+ }
+ }
/* A page supremum record cannot be in the result set: skip
- it now when we have placed a possible lock on it */
+ it now that we have placed a possible lock on it */
goto next_rec;
}
- ut_ad(page_rec_is_user_rec(rec));
+ /*-------------------------------------------------------------*/
+ /* Do sanity checks in case our cursor has bumped into page
+ corruption */
+
+ next_offs = rec_get_next_offs(rec);
+
+ if (next_offs >= UNIV_PAGE_SIZE || next_offs < PAGE_SUPREMUM) {
+
+ if (srv_force_recovery == 0 || moves_up == FALSE) {
+ ut_print_timestamp(stderr);
+ fprintf(stderr,
+" InnoDB: Index corruption: rec offs %lu next offs %lu, page no %lu,\n"
+"InnoDB: index %s, table %s. Run CHECK TABLE to table. You may need to\n"
+"InnoDB: restore from a backup, or dump + drop + reimport the table.\n",
+ (ulint)(rec - buf_frame_align(rec)), next_offs,
+ buf_frame_get_page_no(rec), index->name,
+ index->table_name);
+
+ err = DB_CORRUPTION;
- if (unique_search_from_clust_index && btr_pcur_get_up_match(pcur)
- == dtuple_get_n_fields(search_tuple)) {
- /* The record matches enough */
+ goto lock_wait_or_error;
+ } else {
+ /* The user may be dumping a corrupt table. Jump
+ over the corruption to recover as much as possible. */
- ut_ad(mode == PAGE_CUR_GE);
-#ifdef UNIV_SEARCH_DEBUG
- ut_a(0 == cmp_dtuple_rec(search_tuple, rec));
-#endif
- } else if (match_mode == ROW_SEL_EXACT) {
+ fprintf(stderr,
+"InnoDB: Index corruption: rec offs %lu next offs %lu, page no %lu,\n"
+"InnoDB: index %s, table %s. We try to skip the rest of the page.\n",
+ (ulint)(rec - buf_frame_align(rec)), next_offs,
+ buf_frame_get_page_no(rec), index->name,
+ index->table_name);
+
+ btr_pcur_move_to_last_on_page(pcur, &mtr);
+
+ goto next_rec;
+ }
+ }
+
+ if (srv_force_recovery > 0) {
+ if (!rec_validate(rec) || !btr_index_rec_validate(rec, index,
+ FALSE)) {
+ fprintf(stderr,
+"InnoDB: Index record corruption: rec offs %lu next offs %lu, page no %lu,\n"
+"InnoDB: index %s, table %s. We try to skip the record.\n",
+ (ulint)(rec - buf_frame_align(rec)), next_offs,
+ buf_frame_get_page_no(rec), index->name,
+ index->table_name);
+
+ goto next_rec;
+ }
+ }
+
+ /*-------------------------------------------------------------*/
+
+ /* Note that we cannot trust the up_match value in the cursor at this
+ place because we can arrive here after moving the cursor! Thus
+ we have to recompare rec and search_tuple to determine if they
+ match enough. */
+
+ if (match_mode == ROW_SEL_EXACT) {
/* Test if the index record matches completely to search_tuple
in prebuilt: if not, then we return with DB_RECORD_NOT_FOUND */
@@ -2850,6 +2985,19 @@ rec_loop:
if (0 != cmp_dtuple_rec(search_tuple, rec)) {
+ if (prebuilt->select_lock_type != LOCK_NONE
+ && set_also_gap_locks) {
+ /* Try to place a lock on the index record */
+
+ err = sel_set_rec_lock(rec, index,
+ prebuilt->select_lock_type,
+ LOCK_GAP, thr);
+ if (err != DB_SUCCESS) {
+
+ goto lock_wait_or_error;
+ }
+ }
+
btr_pcur_store_position(pcur, &mtr);
ret = DB_RECORD_NOT_FOUND;
@@ -2862,6 +3010,19 @@ rec_loop:
if (!cmp_dtuple_is_prefix_of_rec(search_tuple, rec)) {
+ if (prebuilt->select_lock_type != LOCK_NONE
+ && set_also_gap_locks) {
+ /* Try to place a lock on the index record */
+
+ err = sel_set_rec_lock(rec, index,
+ prebuilt->select_lock_type,
+ LOCK_GAP, thr);
+ if (err != DB_SUCCESS) {
+
+ goto lock_wait_or_error;
+ }
+ }
+
btr_pcur_store_position(pcur, &mtr);
ret = DB_RECORD_NOT_FOUND;
@@ -2874,16 +3035,39 @@ rec_loop:
/* We are ready to look at a possible new index entry in the result
set: the cursor is now placed on a user record */
- /* Get the right version of the row in a consistent read */
-
- if (prebuilt->select_lock_type == LOCK_NONE) {
+ if (prebuilt->select_lock_type != LOCK_NONE) {
+ /* Try to place a lock on the index record; note that delete
+ marked records are a special case in a unique search. If there
+ is a non-delete marked record, then it is enough to lock its
+ existence with LOCK_REC_NOT_GAP. */
+
+ if (!set_also_gap_locks
+ || (unique_search && !rec_get_deleted_flag(rec))) {
+ err = sel_set_rec_lock(rec, index,
+ prebuilt->select_lock_type,
+ LOCK_REC_NOT_GAP, thr);
+ } else {
+ err = sel_set_rec_lock(rec, index,
+ prebuilt->select_lock_type,
+ LOCK_ORDINARY, thr);
+ }
+
+ if (err != DB_SUCCESS) {
+ goto lock_wait_or_error;
+ }
+ } else {
/* This is a non-locking consistent read: if necessary, fetch
a previous version of the record */
cons_read_requires_clust_rec = FALSE;
- if (index == clust_index) {
+ if (trx->isolation_level == TRX_ISO_READ_UNCOMMITTED) {
+
+ /* Do nothing: we let a non-locking SELECT read the
+ latest version of the record */
+
+ } else if (index == clust_index) {
if (!lock_clust_rec_cons_read_sees(rec, index,
trx->read_view)) {
@@ -3020,8 +3204,11 @@ got_row:
ret = DB_SUCCESS;
goto normal_return;
- /*-------------------------------------------------------------*/
+
next_rec:
+ /*-------------------------------------------------------------*/
+ /* PHASE 5: Move the cursor to the next index record */
+
if (mtr_has_extra_clust_latch) {
/* We must commit mtr if we are moving to the next
non-clustered index record, because we could break the
@@ -3064,8 +3251,10 @@ next_rec:
cnt++;
goto rec_loop;
- /*-------------------------------------------------------------*/
+
lock_wait_or_error:
+ /*-------------------------------------------------------------*/
+
btr_pcur_store_position(pcur, &mtr);
mtr_commit(&mtr);
@@ -3096,6 +3285,7 @@ lock_wait_or_error:
return(err);
normal_return:
+ /*-------------------------------------------------------------*/
que_thr_stop_for_mysql_no_error(thr, trx);
mtr_commit(&mtr);
@@ -3156,10 +3346,12 @@ row_search_check_if_query_cache_permitted(
ret = TRUE;
- /* Assign a read view for the transaction if it does not yet
- have one */
+ /* If the isolation level is high, assign a read view for the
+ transaction if it does not yet have one */
+
+ if (trx->isolation_level >= TRX_ISO_REPEATABLE_READ
+ && !trx->read_view) {
- if (!trx->read_view) {
trx->read_view = read_view_open_now(trx,
trx->read_view_heap);
}
diff --git a/innobase/row/row0uins.c b/innobase/row/row0uins.c
index 9990f893432..fff67dcd627 100644
--- a/innobase/row/row0uins.c
+++ b/innobase/row/row0uins.c
@@ -254,7 +254,8 @@ row_undo_ins_parse_undo_rec(
node->table = dict_table_get_on_id(table_id, node->trx);
if (node->table == NULL) {
- return;
+
+ return;
}
clust_index = dict_table_get_first_index(node->table);
@@ -281,7 +282,7 @@ row_undo_ins(
ut_ad(node && thr);
ut_ad(node->state == UNDO_NODE_INSERT);
-
+
row_undo_ins_parse_undo_rec(node, thr);
if (node->table == NULL) {
@@ -292,6 +293,7 @@ row_undo_ins(
if (!found) {
trx_undo_rec_release(node->trx, node->undo_no);
+
return(DB_SUCCESS);
}
diff --git a/innobase/row/row0undo.c b/innobase/row/row0undo.c
index 5119254f405..01b0b1ab41e 100644
--- a/innobase/row/row0undo.c
+++ b/innobase/row/row0undo.c
@@ -24,6 +24,7 @@ Created 1/8/1997 Heikki Tuuri
#include "row0row.h"
#include "row0uins.h"
#include "row0umod.h"
+#include "row0mysql.h"
#include "srv0srv.h"
/* How to undo row operations?
@@ -204,6 +205,7 @@ row_undo(
ulint err;
trx_t* trx;
dulint roll_ptr;
+ ibool froze_data_dict = FALSE;
ut_ad(node && thr);
@@ -211,7 +213,6 @@ row_undo(
if (node->state == UNDO_NODE_FETCH_NEXT) {
- /* The call below also starts &mtr */
node->undo_rec = trx_roll_pop_top_rec_of_trx(trx,
trx->roll_limit,
&roll_ptr,
@@ -254,6 +255,18 @@ row_undo(
}
}
+ /* Prevent DROP TABLE etc. while we are rolling back this row.
+ If we are doing a TABLE CREATE or some other dictionary operation,
+ then we already have dict_operation_lock locked in x-mode. Do not
+ try to lock again in s-mode, because that would cause a hang. */
+
+ if (trx->dict_operation_lock_mode == 0) {
+
+ row_mysql_freeze_data_dictionary(trx);
+
+ froze_data_dict = TRUE;
+ }
+
if (node->state == UNDO_NODE_INSERT) {
err = row_undo_ins(node, thr);
@@ -264,6 +277,11 @@ row_undo(
err = row_undo_mod(node, thr);
}
+ if (froze_data_dict) {
+
+ row_mysql_unfreeze_data_dictionary(trx);
+ }
+
/* Do some cleanup */
btr_pcur_close(&(node->pcur));
diff --git a/innobase/row/row0upd.c b/innobase/row/row0upd.c
index 25c82f39da9..1231c94da63 100644
--- a/innobase/row/row0upd.c
+++ b/innobase/row/row0upd.c
@@ -79,7 +79,7 @@ ibool
row_upd_index_is_referenced(
/*========================*/
/* out: TRUE if referenced; NOTE that since
- we do not hold dict_foreign_key_check_lock
+ we do not hold dict_operation_lock
when leaving the function, it may be that
the referencing table has been dropped when
we leave this function: this function is only
@@ -89,14 +89,16 @@ row_upd_index_is_referenced(
{
dict_table_t* table = index->table;
dict_foreign_t* foreign;
+ ibool froze_data_dict = FALSE;
if (!UT_LIST_GET_FIRST(table->referenced_list)) {
return(FALSE);
}
- if (!trx->has_dict_foreign_key_check_lock) {
- rw_lock_s_lock(&dict_foreign_key_check_lock);
+ if (trx->dict_operation_lock_mode == 0) {
+ row_mysql_freeze_data_dictionary(trx);
+ froze_data_dict = TRUE;
}
foreign = UT_LIST_GET_FIRST(table->referenced_list);
@@ -104,8 +106,8 @@ row_upd_index_is_referenced(
while (foreign) {
if (foreign->referenced_index == index) {
- if (!trx->has_dict_foreign_key_check_lock) {
- rw_lock_s_unlock(&dict_foreign_key_check_lock);
+ if (froze_data_dict) {
+ row_mysql_unfreeze_data_dictionary(trx);
}
return(TRUE);
@@ -114,8 +116,8 @@ row_upd_index_is_referenced(
foreign = UT_LIST_GET_NEXT(referenced_list, foreign);
}
- if (!trx->has_dict_foreign_key_check_lock) {
- rw_lock_s_unlock(&dict_foreign_key_check_lock);
+ if (froze_data_dict) {
+ row_mysql_unfreeze_data_dictionary(trx);
}
return(FALSE);
@@ -162,12 +164,10 @@ row_upd_check_references_constraints(
mtr_start(mtr);
- if (!trx->has_dict_foreign_key_check_lock) {
+ if (trx->dict_operation_lock_mode == 0) {
got_s_lock = TRUE;
- rw_lock_s_lock(&dict_foreign_key_check_lock);
-
- trx->has_dict_foreign_key_check_lock = TRUE;
+ row_mysql_freeze_data_dictionary(trx);
}
foreign = UT_LIST_GET_FIRST(table->referenced_list);
@@ -189,7 +189,7 @@ row_upd_check_references_constraints(
}
/* NOTE that if the thread ends up waiting for a lock
- we will release dict_foreign_key_check_lock
+ we will release dict_operation_lock
temporarily! But the counter on the table
protects 'foreign' from being dropped while the check
is running. */
@@ -211,10 +211,7 @@ row_upd_check_references_constraints(
if (err != DB_SUCCESS) {
if (got_s_lock) {
- rw_lock_s_unlock(
- &dict_foreign_key_check_lock);
- trx->has_dict_foreign_key_check_lock
- = FALSE;
+ row_mysql_unfreeze_data_dictionary(trx);
}
mem_heap_free(heap);
@@ -227,8 +224,7 @@ row_upd_check_references_constraints(
}
if (got_s_lock) {
- rw_lock_s_unlock(&dict_foreign_key_check_lock);
- trx->has_dict_foreign_key_check_lock = FALSE;
+ row_mysql_unfreeze_data_dictionary(trx);
}
mem_heap_free(heap);