Commit ddf518c5 authored by Yasufumi Kinoshita's avatar Yasufumi Kinoshita

Bug#59354 : Bug #12659252 : ASSERT !OTHER_LOCK AT LOCK_REC_ADD_TO_QUEUE DURING A DELETE OPERATION

The converted implicit lock should wait for the prior conflicting lock if found.

rb://1437 approved by Marko
parents cfebb645 8dd87e69
SET @start_global_value = @@global.innodb_trx_purge_view_update_only_debug;
SELECT @start_global_value;
@start_global_value
0
select @@global.innodb_trx_purge_view_update_only_debug in (0, 1);
@@global.innodb_trx_purge_view_update_only_debug in (0, 1)
1
select @@global.innodb_trx_purge_view_update_only_debug;
@@global.innodb_trx_purge_view_update_only_debug
0
select @@session.innodb_trx_purge_view_update_only_debug;
ERROR HY000: Variable 'innodb_trx_purge_view_update_only_debug' is a GLOBAL variable
show global variables like 'innodb_trx_purge_view_update_only_debug';
Variable_name Value
innodb_trx_purge_view_update_only_debug OFF
show session variables like 'innodb_trx_purge_view_update_only_debug';
Variable_name Value
innodb_trx_purge_view_update_only_debug OFF
select * from information_schema.global_variables where variable_name='innodb_trx_purge_view_update_only_debug';
VARIABLE_NAME VARIABLE_VALUE
INNODB_TRX_PURGE_VIEW_UPDATE_ONLY_DEBUG OFF
select * from information_schema.session_variables where variable_name='innodb_trx_purge_view_update_only_debug';
VARIABLE_NAME VARIABLE_VALUE
INNODB_TRX_PURGE_VIEW_UPDATE_ONLY_DEBUG OFF
set global innodb_trx_purge_view_update_only_debug=1;
select @@global.innodb_trx_purge_view_update_only_debug;
@@global.innodb_trx_purge_view_update_only_debug
1
select * from information_schema.global_variables where variable_name='innodb_trx_purge_view_update_only_debug';
VARIABLE_NAME VARIABLE_VALUE
INNODB_TRX_PURGE_VIEW_UPDATE_ONLY_DEBUG ON
select * from information_schema.session_variables where variable_name='innodb_trx_purge_view_update_only_debug';
VARIABLE_NAME VARIABLE_VALUE
INNODB_TRX_PURGE_VIEW_UPDATE_ONLY_DEBUG ON
set @@global.innodb_trx_purge_view_update_only_debug=0;
select @@global.innodb_trx_purge_view_update_only_debug;
@@global.innodb_trx_purge_view_update_only_debug
0
select * from information_schema.global_variables where variable_name='innodb_trx_purge_view_update_only_debug';
VARIABLE_NAME VARIABLE_VALUE
INNODB_TRX_PURGE_VIEW_UPDATE_ONLY_DEBUG OFF
select * from information_schema.session_variables where variable_name='innodb_trx_purge_view_update_only_debug';
VARIABLE_NAME VARIABLE_VALUE
INNODB_TRX_PURGE_VIEW_UPDATE_ONLY_DEBUG OFF
set session innodb_trx_purge_view_update_only_debug='some';
ERROR HY000: Variable 'innodb_trx_purge_view_update_only_debug' is a GLOBAL variable and should be set with SET GLOBAL
set @@session.innodb_trx_purge_view_update_only_debug='some';
ERROR HY000: Variable 'innodb_trx_purge_view_update_only_debug' is a GLOBAL variable and should be set with SET GLOBAL
set global innodb_trx_purge_view_update_only_debug=1.1;
ERROR 42000: Incorrect argument type to variable 'innodb_trx_purge_view_update_only_debug'
set global innodb_trx_purge_view_update_only_debug='foo';
ERROR 42000: Variable 'innodb_trx_purge_view_update_only_debug' can't be set to the value of 'foo'
set global innodb_trx_purge_view_update_only_debug=-2;
set global innodb_trx_purge_view_update_only_debug=1e1;
ERROR 42000: Incorrect argument type to variable 'innodb_trx_purge_view_update_only_debug'
set global innodb_trx_purge_view_update_only_debug=2;
ERROR 42000: Variable 'innodb_trx_purge_view_update_only_debug' can't be set to the value of '2'
SET @@global.innodb_trx_purge_view_update_only_debug = @start_global_value;
SELECT @@global.innodb_trx_purge_view_update_only_debug;
@@global.innodb_trx_purge_view_update_only_debug
0
--source include/have_innodb.inc
--source include/have_debug.inc
SET @start_global_value = @@global.innodb_trx_purge_view_update_only_debug;
SELECT @start_global_value;
#
# exists as global only
#
select @@global.innodb_trx_purge_view_update_only_debug in (0, 1);
select @@global.innodb_trx_purge_view_update_only_debug;
--error ER_INCORRECT_GLOBAL_LOCAL_VAR
select @@session.innodb_trx_purge_view_update_only_debug;
show global variables like 'innodb_trx_purge_view_update_only_debug';
show session variables like 'innodb_trx_purge_view_update_only_debug';
select * from information_schema.global_variables where variable_name='innodb_trx_purge_view_update_only_debug';
select * from information_schema.session_variables where variable_name='innodb_trx_purge_view_update_only_debug';
#
# show that it's writable
#
set global innodb_trx_purge_view_update_only_debug=1;
select @@global.innodb_trx_purge_view_update_only_debug;
select * from information_schema.global_variables where variable_name='innodb_trx_purge_view_update_only_debug';
select * from information_schema.session_variables where variable_name='innodb_trx_purge_view_update_only_debug';
set @@global.innodb_trx_purge_view_update_only_debug=0;
select @@global.innodb_trx_purge_view_update_only_debug;
select * from information_schema.global_variables where variable_name='innodb_trx_purge_view_update_only_debug';
select * from information_schema.session_variables where variable_name='innodb_trx_purge_view_update_only_debug';
--error ER_GLOBAL_VARIABLE
set session innodb_trx_purge_view_update_only_debug='some';
--error ER_GLOBAL_VARIABLE
set @@session.innodb_trx_purge_view_update_only_debug='some';
#
# incorrect types
#
--error ER_WRONG_TYPE_FOR_VAR
set global innodb_trx_purge_view_update_only_debug=1.1;
--error ER_WRONG_VALUE_FOR_VAR
set global innodb_trx_purge_view_update_only_debug='foo';
set global innodb_trx_purge_view_update_only_debug=-2;
--error ER_WRONG_TYPE_FOR_VAR
set global innodb_trx_purge_view_update_only_debug=1e1;
--error ER_WRONG_VALUE_FOR_VAR
set global innodb_trx_purge_view_update_only_debug=2;
#
# Cleanup
#
SET @@global.innodb_trx_purge_view_update_only_debug = @start_global_value;
SELECT @@global.innodb_trx_purge_view_update_only_debug;
......@@ -696,6 +696,8 @@ static SHOW_VAR innodb_status_variables[]= {
#ifdef UNIV_DEBUG
{"purge_trx_id_age",
(char*) &export_vars.innodb_purge_trx_id_age, SHOW_LONG},
{"purge_view_trx_id_age",
(char*) &export_vars.innodb_purge_view_trx_id_age, SHOW_LONG},
#endif /* UNIV_DEBUG */
{NullS, NullS, SHOW_LONG}
};
......@@ -11689,6 +11691,13 @@ static MYSQL_SYSVAR_UINT(limit_optimistic_insert_debug,
btr_cur_limit_optimistic_insert_debug, PLUGIN_VAR_RQCMDARG,
"Artificially limit the number of records per B-tree page (0=unlimited).",
NULL, NULL, 0, 0, UINT_MAX32, 0);
static MYSQL_SYSVAR_BOOL(trx_purge_view_update_only_debug,
srv_purge_view_update_only_debug, PLUGIN_VAR_NOCMDARG,
"Pause actual purging any delete-marked records, but merely update the purge view. "
"It is to create artificially the situation the purge view have been updated "
"but the each purges were not done yet.",
NULL, NULL, FALSE);
#endif /* UNIV_DEBUG */
static MYSQL_SYSVAR_BOOL(print_all_deadlocks, srv_print_all_deadlocks,
......@@ -11768,6 +11777,7 @@ static struct st_mysql_sys_var* innobase_system_variables[]= {
#ifdef UNIV_DEBUG
MYSQL_SYSVAR(trx_rseg_n_slots_debug),
MYSQL_SYSVAR(limit_optimistic_insert_debug),
MYSQL_SYSVAR(trx_purge_view_update_only_debug),
#endif /* UNIV_DEBUG */
MYSQL_SYSVAR(print_all_deadlocks),
NULL
......
......@@ -797,14 +797,22 @@ lock_rec_get_page_no(
remains set when the waiting lock is granted,
or if the lock is inherited to a neighboring
record */
#if (LOCK_WAIT|LOCK_GAP|LOCK_REC_NOT_GAP|LOCK_INSERT_INTENTION)&LOCK_MODE_MASK
#define LOCK_CONV_BY_OTHER 4096 /*!< this bit is set when the lock is created
by other transaction */
#if (LOCK_WAIT|LOCK_GAP|LOCK_REC_NOT_GAP|LOCK_INSERT_INTENTION|LOCK_CONV_BY_OTHER)&LOCK_MODE_MASK
# error
#endif
#if (LOCK_WAIT|LOCK_GAP|LOCK_REC_NOT_GAP|LOCK_INSERT_INTENTION)&LOCK_TYPE_MASK
#if (LOCK_WAIT|LOCK_GAP|LOCK_REC_NOT_GAP|LOCK_INSERT_INTENTION|LOCK_CONV_BY_OTHER)&LOCK_TYPE_MASK
# error
#endif
/* @} */
/** Checks if this is a waiting lock created by lock->trx itself.
@param type_mode lock->type_mode
@return whether it is a waiting lock belonging to lock->trx */
#define lock_is_wait_not_by_other(type_mode) \
((type_mode & (LOCK_CONV_BY_OTHER | LOCK_WAIT)) == LOCK_WAIT)
/** Lock operation struct */
typedef struct lock_op_struct lock_op_t;
/** Lock operation struct */
......
......@@ -265,6 +265,10 @@ extern ulint srv_fatal_semaphore_wait_threshold;
#define SRV_SEMAPHORE_WAIT_EXTENSION 7200
extern ulint srv_dml_needed_delay;
#ifdef UNIV_DEBUG
extern my_bool srv_purge_view_update_only_debug;
#endif /* UNIV_DEBUG */
extern mutex_t* kernel_mutex_temp;/* mutex protecting the server, trx structs,
query threads, and lock table: we allocate
it from dynamic memory to get it to the
......@@ -747,6 +751,8 @@ struct export_var_struct{
ulint innodb_truncated_status_writes; /*!< srv_truncated_status_writes */
#ifdef UNIV_DEBUG
ulint innodb_purge_trx_id_age; /*!< max_trx_id - purged trx_id */
ulint innodb_purge_view_trx_id_age; /*!< rw_max_trx_id
- purged view's min trx_id */
#endif /* UNIV_DEBUG */
};
......
......@@ -789,12 +789,16 @@ lock_reset_lock_and_trx_wait(
/*=========================*/
lock_t* lock) /*!< in: record lock */
{
ut_ad((lock->trx)->wait_lock == lock);
ut_ad(lock_get_wait(lock));
/* Reset the back pointer in trx to this waiting lock request */
(lock->trx)->wait_lock = NULL;
if (!(lock->type_mode & LOCK_CONV_BY_OTHER)) {
ut_ad((lock->trx)->wait_lock == lock);
(lock->trx)->wait_lock = NULL;
} else {
ut_ad(lock_get_type_low(lock) == LOCK_REC);
}
lock->type_mode &= ~LOCK_WAIT;
}
......@@ -1430,9 +1434,9 @@ lock_rec_has_expl(
while (lock) {
if (lock->trx == trx
&& !lock_is_wait_not_by_other(lock->type_mode)
&& lock_mode_stronger_or_eq(lock_get_mode(lock),
precise_mode & LOCK_MODE_MASK)
&& !lock_get_wait(lock)
&& (!lock_rec_get_rec_not_gap(lock)
|| (precise_mode & LOCK_REC_NOT_GAP)
|| heap_no == PAGE_HEAP_NO_SUPREMUM)
......@@ -1730,7 +1734,7 @@ lock_rec_create(
HASH_INSERT(lock_t, hash, lock_sys->rec_hash,
lock_rec_fold(space, page_no), lock);
if (UNIV_UNLIKELY(type_mode & LOCK_WAIT)) {
if (lock_is_wait_not_by_other(type_mode)) {
lock_set_lock_and_trx_wait(lock, trx);
}
......@@ -1761,10 +1765,11 @@ lock_rec_enqueue_waiting(
const buf_block_t* block, /*!< in: buffer block containing
the record */
ulint heap_no,/*!< in: heap number of the record */
lock_t* lock, /*!< in: lock object; NULL if a new
one should be created. */
dict_index_t* index, /*!< in: index of record */
que_thr_t* thr) /*!< in: query thread */
{
lock_t* lock;
trx_t* trx;
ut_ad(mutex_own(&kernel_mutex));
......@@ -1799,9 +1804,17 @@ lock_rec_enqueue_waiting(
ut_ad(0);
}
/* Enqueue the lock request that will wait to be granted */
lock = lock_rec_create(type_mode | LOCK_WAIT,
block, heap_no, index, trx);
if (lock == NULL) {
/* Enqueue the lock request that will wait to be granted */
lock = lock_rec_create(type_mode | LOCK_WAIT,
block, heap_no, index, trx);
} else {
ut_ad(lock->type_mode & LOCK_WAIT);
ut_ad(lock->type_mode & LOCK_CONV_BY_OTHER);
lock->type_mode &= ~LOCK_CONV_BY_OTHER;
lock_set_lock_and_trx_wait(lock, trx);
}
/* Check if a deadlock occurs: if yes, remove the lock request and
return an error code */
......@@ -2046,6 +2059,7 @@ lock_rec_lock_slow(
que_thr_t* thr) /*!< in: query thread */
{
trx_t* trx;
lock_t* lock;
ut_ad(mutex_own(&kernel_mutex));
ut_ad((LOCK_MODE_MASK & mode) != LOCK_S
......@@ -2060,7 +2074,27 @@ lock_rec_lock_slow(
trx = thr_get_trx(thr);
if (lock_rec_has_expl(mode, block, heap_no, trx)) {
lock = lock_rec_has_expl(mode, block, heap_no, trx);
if (lock) {
if (lock->type_mode & LOCK_CONV_BY_OTHER) {
/* This lock or lock waiting was created by the other
transaction, not by the transaction (trx) itself.
So, the transaction (trx) should treat it collectly
according as whether granted or not. */
if (lock->type_mode & LOCK_WAIT) {
/* This lock request was not granted yet.
Should wait for granted. */
goto enqueue_waiting;
} else {
/* This lock request was already granted.
Just clearing the flag. */
lock->type_mode &= ~LOCK_CONV_BY_OTHER;
}
}
/* The trx already has a strong enough lock on rec: do
nothing */
......@@ -2070,8 +2104,10 @@ lock_rec_lock_slow(
the queue, as this transaction does not have a lock strong
enough already granted on the record, we have to wait. */
ut_ad(lock == NULL);
enqueue_waiting:
return(lock_rec_enqueue_waiting(mode, block, heap_no,
index, thr));
lock, index, thr));
} else if (!impl) {
/* Set the requested lock on the record */
......@@ -2213,7 +2249,8 @@ lock_grant(
TRX_QUE_LOCK_WAIT state, and there is no need to end the lock wait
for it */
if (lock->trx->que_state == TRX_QUE_LOCK_WAIT) {
if (!(lock->type_mode & LOCK_CONV_BY_OTHER)
&& lock->trx->que_state == TRX_QUE_LOCK_WAIT) {
trx_end_lock_wait(lock->trx);
}
}
......@@ -2230,6 +2267,7 @@ lock_rec_cancel(
{
ut_ad(mutex_own(&kernel_mutex));
ut_ad(lock_get_type_low(lock) == LOCK_REC);
ut_ad(!(lock->type_mode & LOCK_CONV_BY_OTHER));
/* Reset the bit (there can be only one set bit) in the lock bitmap */
lock_rec_reset_nth_bit(lock, lock_rec_find_set_bit(lock));
......@@ -2372,8 +2410,12 @@ lock_rec_reset_and_release_wait(
lock = lock_rec_get_first(block, heap_no);
while (lock != NULL) {
if (lock_get_wait(lock)) {
if (lock_is_wait_not_by_other(lock->type_mode)) {
lock_rec_cancel(lock);
} else if (lock_get_wait(lock)) {
/* just reset LOCK_WAIT */
lock_rec_reset_nth_bit(lock, heap_no);
lock_reset_lock_and_trx_wait(lock);
} else {
lock_rec_reset_nth_bit(lock, heap_no);
}
......@@ -3646,6 +3688,7 @@ lock_table_create(
ut_ad(table && trx);
ut_ad(mutex_own(&kernel_mutex));
ut_ad(!(type_mode & LOCK_CONV_BY_OTHER));
if ((type_mode & LOCK_MODE_MASK) == LOCK_AUTO_INC) {
++table->n_waiting_or_granted_auto_inc_locks;
......@@ -4199,6 +4242,7 @@ lock_cancel_waiting_and_release(
lock_t* lock) /*!< in: waiting lock request */
{
ut_ad(mutex_own(&kernel_mutex));
ut_ad(!(lock->type_mode & LOCK_CONV_BY_OTHER));
if (lock_get_type_low(lock) == LOCK_REC) {
......@@ -5240,7 +5284,7 @@ lock_rec_insert_check_and_lock(
err = lock_rec_enqueue_waiting(LOCK_X | LOCK_GAP
| LOCK_INSERT_INTENTION,
block, next_rec_heap_no,
index, thr);
NULL, index, thr);
} else {
err = DB_SUCCESS;
}
......@@ -5316,10 +5360,23 @@ lock_rec_convert_impl_to_expl(
if (!lock_rec_has_expl(LOCK_X | LOCK_REC_NOT_GAP, block,
heap_no, impl_trx)) {
ulint type_mode = (LOCK_REC | LOCK_X
| LOCK_REC_NOT_GAP);
/* If the delete-marked record was locked already,
we should reserve lock waiting for impl_trx as
implicit lock. Because cannot lock at this moment.*/
if (rec_get_deleted_flag(rec, rec_offs_comp(offsets))
&& lock_rec_other_has_conflicting(
LOCK_X | LOCK_REC_NOT_GAP, block,
heap_no, impl_trx)) {
type_mode |= (LOCK_WAIT | LOCK_CONV_BY_OTHER);
}
lock_rec_add_to_queue(
LOCK_REC | LOCK_X | LOCK_REC_NOT_GAP,
block, heap_no, index, impl_trx);
type_mode, block, heap_no, index, impl_trx);
}
}
}
......
......@@ -2254,7 +2254,10 @@ row_ins_index_entry(
err = row_ins_index_entry_low(BTR_MODIFY_LEAF, index, entry,
n_ext, thr);
if (err != DB_FAIL) {
if (index == dict_table_get_first_index(index->table)
&& thr_get_trx(thr)->mysql_thd != 0) {
DEBUG_SYNC_C("row_ins_clust_index_entry_leaf_after");
}
return(err);
}
......
......@@ -2103,6 +2103,14 @@ srv_export_innodb_status(void)
export_vars.innodb_purge_trx_id_age =
trx_sys->max_trx_id - purge_sys->done_trx_no;
}
if (!purge_sys->view
|| trx_sys->max_trx_id < purge_sys->view->up_limit_id) {
export_vars.innodb_purge_view_trx_id_age = 0;
} else {
export_vars.innodb_purge_view_trx_id_age =
trx_sys->max_trx_id - purge_sys->view->up_limit_id;
}
#endif /* UNIV_DEBUG */
mutex_exit(&srv_innodb_monitor_mutex);
......
......@@ -61,6 +61,10 @@ UNIV_INTERN mysql_pfs_key_t trx_purge_latch_key;
UNIV_INTERN mysql_pfs_key_t purge_sys_bh_mutex_key;
#endif /* UNIV_PFS_MUTEX */
#ifdef UNIV_DEBUG
UNIV_INTERN my_bool srv_purge_view_update_only_debug;
#endif /* UNIV_DEBUG */
/*****************************************************************//**
Checks if trx_id is >= purge_view: then it is guaranteed that its update
undo log still exists in the system.
......@@ -1180,6 +1184,12 @@ trx_purge(
rw_lock_x_unlock(&(purge_sys->latch));
#ifdef UNIV_DEBUG
if (srv_purge_view_update_only_debug) {
return(0);
}
#endif
purge_sys->state = TRX_PURGE_ON;
purge_sys->handle_limit = purge_sys->n_pages_handled + limit;
......
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