row0vers.c, row0sel.c, row0ins.c:

  Fix bug: InnoDB could print that it cannot find a clustered index record if an update undo, purge, and a consistent read coincided, in rare cases it might also have returned a wrong row in a query
parent 4deb135b
...@@ -791,27 +791,28 @@ row_ins_foreign_check_on_constraint( ...@@ -791,27 +791,28 @@ row_ins_foreign_check_on_constraint(
mem_heap_free(tmp_heap); mem_heap_free(tmp_heap);
clust_rec = btr_pcur_get_rec(cascade->pcur); clust_rec = btr_pcur_get_rec(cascade->pcur);
}
if (!page_rec_is_user_rec(clust_rec)) { if (btr_pcur_get_low_match(cascade->pcur)
fprintf(stderr, < dict_index_get_n_unique(clust_index)) {
fprintf(stderr,
"InnoDB: error in cascade of a foreign key op\n" "InnoDB: error in cascade of a foreign key op\n"
"InnoDB: index %s table %s\n", index->name, "InnoDB: index %s table %s\n", index->name,
index->table->name); index->table->name);
rec_sprintf(err_buf, 900, rec); rec_sprintf(err_buf, 900, rec);
fprintf(stderr, "InnoDB: record %s\n", err_buf); fprintf(stderr, "InnoDB: record %s\n", err_buf);
rec_sprintf(err_buf, 900, clust_rec);
fprintf(stderr, "InnoDB: clustered record %s\n", err_buf);
fprintf(stderr, rec_sprintf(err_buf, 900, clust_rec);
fprintf(stderr, "InnoDB: clustered record %s\n",
err_buf);
fprintf(stderr,
"InnoDB: Make a detailed bug report and send it\n"); "InnoDB: Make a detailed bug report and send it\n");
fprintf(stderr, "InnoDB: to mysql@lists.mysql.com\n"); fprintf(stderr, "InnoDB: to mysql@lists.mysql.com\n");
err = DB_SUCCESS; err = DB_SUCCESS;
goto nonstandard_exit_func; goto nonstandard_exit_func;
}
} }
/* Set an X-lock on the row to delete or update in the child table */ /* Set an X-lock on the row to delete or update in the child table */
......
...@@ -609,7 +609,25 @@ row_sel_get_clust_rec( ...@@ -609,7 +609,25 @@ row_sel_get_clust_rec(
clust_rec = btr_pcur_get_rec(&(plan->clust_pcur)); clust_rec = btr_pcur_get_rec(&(plan->clust_pcur));
ut_ad(page_rec_is_user_rec(clust_rec)); if (btr_pcur_get_low_match(&(plan->clust_pcur))
< dict_index_get_n_unique(index)) {
ut_a(rec_get_deleted_flag(rec));
ut_a(node->read_view);
/* In a rare case it is possible that no clust rec is found
for a delete-marked secondary index record: if in row0umod.c
in row_undo_mod_remove_clust_low() we have already removed
the clust rec, while purge is still cleaning and removing
secondary index records associated with earlier versions of
the clustered index record. In that case we know that the
clustered index record did not exist in the read view of
trx. */
clust_rec = NULL;
goto func_exit;
}
if (!node->read_view) { if (!node->read_view) {
/* Try to place a lock on the index record */ /* Try to place a lock on the index record */
...@@ -672,6 +690,7 @@ row_sel_get_clust_rec( ...@@ -672,6 +690,7 @@ row_sel_get_clust_rec(
row_sel_fetch_columns(index, clust_rec, row_sel_fetch_columns(index, clust_rec,
UT_LIST_GET_FIRST(plan->columns)); UT_LIST_GET_FIRST(plan->columns));
func_exit:
*out_rec = clust_rec; *out_rec = clust_rec;
return(DB_SUCCESS); return(DB_SUCCESS);
...@@ -1253,6 +1272,8 @@ row_sel( ...@@ -1253,6 +1272,8 @@ row_sel(
/* PHASE 3: Get previous version in a consistent read */ /* PHASE 3: Get previous version in a consistent read */
cons_read_requires_clust_rec = FALSE;
if (consistent_read) { if (consistent_read) {
/* This is a non-locking consistent read: if necessary, fetch /* This is a non-locking consistent read: if necessary, fetch
a previous version of the record */ a previous version of the record */
...@@ -2269,7 +2290,10 @@ row_sel_get_clust_rec_for_mysql( ...@@ -2269,7 +2290,10 @@ row_sel_get_clust_rec_for_mysql(
/* out: DB_SUCCESS or error code */ /* out: DB_SUCCESS or error code */
row_prebuilt_t* prebuilt,/* in: prebuilt struct in the handle */ row_prebuilt_t* prebuilt,/* in: prebuilt struct in the handle */
dict_index_t* sec_index,/* in: secondary index where rec resides */ dict_index_t* sec_index,/* in: secondary index where rec resides */
rec_t* rec, /* in: record in a non-clustered index */ rec_t* rec, /* in: record in a non-clustered index; if
this is a locking read, then rec is not
allowed to be delete-marked, and that would
not make sense either */
que_thr_t* thr, /* in: query thread */ que_thr_t* thr, /* in: query thread */
rec_t** out_rec,/* out: clustered record or an old version of rec_t** out_rec,/* out: clustered record or an old version of
it, NULL if the old version did not exist it, NULL if the old version did not exist
...@@ -2285,7 +2309,7 @@ row_sel_get_clust_rec_for_mysql( ...@@ -2285,7 +2309,7 @@ row_sel_get_clust_rec_for_mysql(
ulint err; ulint err;
trx_t* trx; trx_t* trx;
char err_buf[1000]; char err_buf[1000];
*out_rec = NULL; *out_rec = NULL;
row_build_row_ref_in_tuple(prebuilt->clust_ref, sec_index, rec); row_build_row_ref_in_tuple(prebuilt->clust_ref, sec_index, rec);
...@@ -2298,26 +2322,43 @@ row_sel_get_clust_rec_for_mysql( ...@@ -2298,26 +2322,43 @@ row_sel_get_clust_rec_for_mysql(
clust_rec = btr_pcur_get_rec(prebuilt->clust_pcur); clust_rec = btr_pcur_get_rec(prebuilt->clust_pcur);
if (!page_rec_is_user_rec(clust_rec)) { if (btr_pcur_get_low_match(prebuilt->clust_pcur)
ut_print_timestamp(stderr); < dict_index_get_n_unique(clust_index)) {
fprintf(stderr,
" InnoDB: error clustered record for sec rec not found\n" /* In a rare case it is possible that no clust rec is found
"InnoDB: index %s table %s\n", sec_index->name, for a delete-marked secondary index record: if in row0umod.c
sec_index->table->name); in row_undo_mod_remove_clust_low() we have already removed
the clust rec, while purge is still cleaning and removing
secondary index records associated with earlier versions of
the clustered index record. In that case we know that the
clustered index record did not exist in the read view of
trx. */
if (!rec_get_deleted_flag(rec)
|| prebuilt->select_lock_type != LOCK_NONE) {
rec_sprintf(err_buf, 900, rec); ut_print_timestamp(stderr);
fprintf(stderr, "InnoDB: sec index record %s\n", err_buf); 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, clust_rec); rec_sprintf(err_buf, 900, rec);
fprintf(stderr, "InnoDB: clust index record %s\n", err_buf); fprintf(stderr,
"InnoDB: sec index record %s\n", err_buf);
trx = thr_get_trx(thr); rec_sprintf(err_buf, 900, clust_rec);
trx_print(err_buf, trx); fprintf(stderr,
"InnoDB: clust index record %s\n", err_buf);
trx = thr_get_trx(thr);
trx_print(err_buf, trx);
fprintf(stderr, fprintf(stderr,
"%s\nInnoDB: Make a detailed bug report and send it\n", "%s\nInnoDB: Make a detailed bug report and send it\n",
err_buf); err_buf);
fprintf(stderr, "InnoDB: to mysql@lists.mysql.com\n"); fprintf(stderr, "InnoDB: to mysql@lists.mysql.com\n");
}
clust_rec = NULL; clust_rec = NULL;
...@@ -2989,8 +3030,6 @@ row_search_for_mysql( ...@@ -2989,8 +3030,6 @@ row_search_for_mysql(
/*-------------------------------------------------------------*/ /*-------------------------------------------------------------*/
/* PHASE 4: Look for matching records in a loop */ /* PHASE 4: Look for matching records in a loop */
cons_read_requires_clust_rec = FALSE;
rec = btr_pcur_get_rec(pcur); rec = btr_pcur_get_rec(pcur);
/* /*
printf("Using index %s cnt %lu ", index->name, cnt); printf("Using index %s cnt %lu ", index->name, cnt);
...@@ -3145,6 +3184,8 @@ row_search_for_mysql( ...@@ -3145,6 +3184,8 @@ row_search_for_mysql(
/* We are ready to look at a possible new index entry in the result /* We are ready to look at a possible new index entry in the result
set: the cursor is now placed on a user record */ set: the cursor is now placed on a user record */
cons_read_requires_clust_rec = FALSE;
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 /* Try to place a lock on the index record; note that delete
marked records are a special case in a unique search. If there marked records are a special case in a unique search. If there
...@@ -3170,8 +3211,6 @@ row_search_for_mysql( ...@@ -3170,8 +3211,6 @@ row_search_for_mysql(
/* This is a non-locking consistent read: if necessary, fetch /* This is a non-locking consistent read: if necessary, fetch
a previous version of the record */ a previous version of the record */
cons_read_requires_clust_rec = FALSE;
if (trx->isolation_level == TRX_ISO_READ_UNCOMMITTED) { if (trx->isolation_level == TRX_ISO_READ_UNCOMMITTED) {
/* Do nothing: we let a non-locking SELECT read the /* Do nothing: we let a non-locking SELECT read the
...@@ -3215,7 +3254,7 @@ row_search_for_mysql( ...@@ -3215,7 +3254,7 @@ row_search_for_mysql(
if (rec_get_deleted_flag(rec) && !cons_read_requires_clust_rec) { if (rec_get_deleted_flag(rec) && !cons_read_requires_clust_rec) {
/* The record is delete marked: we can skip it if this is /* The record is delete-marked: we can skip it if this is
not a consistent read which might see an earlier version not a consistent read which might see an earlier version
of a non-clustered index record */ of a non-clustered index record */
...@@ -3324,7 +3363,7 @@ row_search_for_mysql( ...@@ -3324,7 +3363,7 @@ row_search_for_mysql(
goto normal_return; goto normal_return;
next_rec: next_rec:
/*-------------------------------------------------------------*/ /*-------------------------------------------------------------*/
/* PHASE 5: Move the cursor to the next index record */ /* PHASE 5: Move the cursor to the next index record */
if (mtr_has_extra_clust_latch) { if (mtr_has_extra_clust_latch) {
......
...@@ -59,7 +59,6 @@ row_vers_impl_x_locked_off_kernel( ...@@ -59,7 +59,6 @@ row_vers_impl_x_locked_off_kernel(
ibool rec_del; ibool rec_del;
ulint err; ulint err;
mtr_t mtr; mtr_t mtr;
char err_buf[1000];
ut_ad(mutex_own(&kernel_mutex)); ut_ad(mutex_own(&kernel_mutex));
ut_ad(!rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED)); ut_ad(!rw_lock_own(&(purge_sys->latch), RW_LOCK_SHARED));
...@@ -77,22 +76,17 @@ row_vers_impl_x_locked_off_kernel( ...@@ -77,22 +76,17 @@ row_vers_impl_x_locked_off_kernel(
clust_rec = row_get_clust_rec(BTR_SEARCH_LEAF, rec, index, clust_rec = row_get_clust_rec(BTR_SEARCH_LEAF, rec, index,
&clust_index, &mtr); &clust_index, &mtr);
if (!clust_rec) { if (!clust_rec) {
rec_sprintf(err_buf, 900, rec); /* In a rare case it is possible that no clust rec is found
for a secondary index record: if in row0umod.c
ut_print_timestamp(stderr); row_undo_mod_remove_clust_low() we have already removed the
fprintf(stderr, clust rec, while purge is still cleaning and removing
" InnoDB: Error: cannot find the clustered index record\n" secondary index records associated with earlier versions of
"InnoDB: for a secondary index record in table %s index %s.\n" the clustered index record. In that case there cannot be
"InnoDB: Secondary index record %s.\n" any implicit lock on the secondary index record, because
"InnoDB: The table is probably corrupt. Please run CHECK TABLE on it.\n" an active transaction which has modified the secondary index
"InnoDB: You can try to repair the table by dump + drop + reimport.\n" record has also modified the clustered index record. And in
"InnoDB: Send a detailed bug report to mysql@lists.mysql.com.\n", a rollback we always undo the modifications to secondary index
index->table_name, index->name, err_buf); records before the clustered index record. */
mutex_enter(&kernel_mutex);
mtr_commit(&mtr);
/* We assume there is no lock on the record, though this
is not certain because the table is apparently corrupt */
return(NULL); return(NULL);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment