This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

Add an option to disable deadlock checking in InnoDB

Summary:
Feature: Performance of locking subsystem

Deadlock detecion code can be expensive when a lots of threads
wait for the same lock i.e.: a hotspot scenario. At times, it is
much more efficient to disable deadlock detection code and rely
on innodb_lock_wait_timeout to rollback in case of deadlock.

This patch adds an option innodb_deadlock_detect (default TRUE)
to disable the deadlock detection code.

Test Plan: mtr

Reviewers: pivanof, steaphan, darnaut

Reviewed By: darnaut

CC: jtolmer, CalvinSun, WebScaleSQL

Differential Revision: https://reviews.facebook.net/D17679
  • Loading branch information...
inaam-rana committed Apr 10, 2014
1 parent 322bb5c commit d9e347dc3db6acb247517896128fec664ab19202
@@ -0,0 +1,38 @@
set @start_lock_wait_value = @@global.innodb_lock_wait_timeout;
set @start_deadlock_detect_value = @@global.innodb_deadlock_detect;
set global innodb_lock_wait_timeout = 2;
set global innodb_deadlock_detect = 0;
# Establish connection con1 (user=root)
# Establish connection con2 (user=root)
drop table if exists t1;
create table t1 (id int primary key) engine = InnoDB;
insert into t1 values(1),(2);
commit;
# Switch to connection con1
# Lock first row in con1
start transaction;
select * from t1 where id = 1 for update;
id
1
# Switch to connection con2
# Lock second row in con2
start transaction;
select * from t1 where id = 2 for update;
id
2
# Try locking first row in con2
update t1 set id = 3 where id = 1;
# Switch to connection con1
# Wait for con2 to start lock wait
# Try locking second row in con1
update t1 set id = 4 where id = 2;
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
commit;
# Switch to connection con2
# reap the update command
ERROR HY000: Lock wait timeout exceeded; try restarting transaction
commit;
# Cleanup
drop table t1;
set global innodb_lock_wait_timeout = @start_lock_wait_value;
set global innodb_deadlock_detect = @start_deadlock_detect_value;
@@ -0,0 +1,68 @@
#
# Check innodb_deadlock_detect functionality
#
--source include/have_innodb.inc

set @start_lock_wait_value = @@global.innodb_lock_wait_timeout;
set @start_deadlock_detect_value = @@global.innodb_deadlock_detect;

set global innodb_lock_wait_timeout = 2;
set global innodb_deadlock_detect = 0;

--echo # Establish connection con1 (user=root)
connect (con1,localhost,root,,);
--echo # Establish connection con2 (user=root)
connect (con2,localhost,root,,);

--disable_warnings
drop table if exists t1;
--enable_warnings

create table t1 (id int primary key) engine = InnoDB;
insert into t1 values(1),(2);
commit;

--echo # Switch to connection con1
connection con1;
--echo # Lock first row in con1
start transaction;
select * from t1 where id = 1 for update;

--echo # Switch to connection con2
connection con2;
--echo # Lock second row in con2
start transaction;
select * from t1 where id = 2 for update;

--echo # Try locking first row in con2
--send
update t1 set id = 3 where id = 1;

--echo # Switch to connection con1
connection con1;
--echo # Wait for con2 to start lock wait
let $wait_condition=
select count(*) = 1 from
information_schema.innodb_lock_waits w, information_schema.innodb_locks l
where w.requested_lock_id = l.lock_id and l.lock_table = '`test`.`t1`';
--source include/wait_condition.inc

--echo # Try locking second row in con1
--error ER_LOCK_WAIT_TIMEOUT
update t1 set id = 4 where id = 2;
commit;

--echo # Switch to connection con2
connection con2;
--echo # reap the update command
--error ER_LOCK_WAIT_TIMEOUT
--reap
commit;

--echo # Cleanup
connection default;
disconnect con1;
disconnect con2;
drop table t1;
set global innodb_lock_wait_timeout = @start_lock_wait_value;
set global innodb_deadlock_detect = @start_deadlock_detect_value;
@@ -0,0 +1,92 @@
SET @start_global_value = @@global.innodb_deadlock_detect;
SELECT @start_global_value;
@start_global_value
1
Valid values are 'ON' and 'OFF'
select @@global.innodb_deadlock_detect in (0, 1);
@@global.innodb_deadlock_detect in (0, 1)
1
select @@global.innodb_deadlock_detect;
@@global.innodb_deadlock_detect
1
select @@session.innodb_deadlock_detect;
ERROR HY000: Variable 'innodb_deadlock_detect' is a GLOBAL variable
show global variables like 'innodb_deadlock_detect';
Variable_name Value
innodb_deadlock_detect ON
show session variables like 'innodb_deadlock_detect';
Variable_name Value
innodb_deadlock_detect ON
select * from information_schema.global_variables where variable_name='innodb_deadlock_detect';
VARIABLE_NAME VARIABLE_VALUE
INNODB_DEADLOCK_DETECT ON
select * from information_schema.session_variables where variable_name='innodb_deadlock_detect';
VARIABLE_NAME VARIABLE_VALUE
INNODB_DEADLOCK_DETECT ON
set global innodb_deadlock_detect='OFF';
select @@global.innodb_deadlock_detect;
@@global.innodb_deadlock_detect
0
select * from information_schema.global_variables where variable_name='innodb_deadlock_detect';
VARIABLE_NAME VARIABLE_VALUE
INNODB_DEADLOCK_DETECT OFF
select * from information_schema.session_variables where variable_name='innodb_deadlock_detect';
VARIABLE_NAME VARIABLE_VALUE
INNODB_DEADLOCK_DETECT OFF
set @@global.innodb_deadlock_detect=1;
select @@global.innodb_deadlock_detect;
@@global.innodb_deadlock_detect
1
select * from information_schema.global_variables where variable_name='innodb_deadlock_detect';
VARIABLE_NAME VARIABLE_VALUE
INNODB_DEADLOCK_DETECT ON
select * from information_schema.session_variables where variable_name='innodb_deadlock_detect';
VARIABLE_NAME VARIABLE_VALUE
INNODB_DEADLOCK_DETECT ON
set global innodb_deadlock_detect=0;
select @@global.innodb_deadlock_detect;
@@global.innodb_deadlock_detect
0
select * from information_schema.global_variables where variable_name='innodb_deadlock_detect';
VARIABLE_NAME VARIABLE_VALUE
INNODB_DEADLOCK_DETECT OFF
select * from information_schema.session_variables where variable_name='innodb_deadlock_detect';
VARIABLE_NAME VARIABLE_VALUE
INNODB_DEADLOCK_DETECT OFF
set @@global.innodb_deadlock_detect='ON';
select @@global.innodb_deadlock_detect;
@@global.innodb_deadlock_detect
1
select * from information_schema.global_variables where variable_name='innodb_deadlock_detect';
VARIABLE_NAME VARIABLE_VALUE
INNODB_DEADLOCK_DETECT ON
select * from information_schema.session_variables where variable_name='innodb_deadlock_detect';
VARIABLE_NAME VARIABLE_VALUE
INNODB_DEADLOCK_DETECT ON
set session innodb_deadlock_detect='OFF';
ERROR HY000: Variable 'innodb_deadlock_detect' is a GLOBAL variable and should be set with SET GLOBAL
set @@session.innodb_deadlock_detect='ON';
ERROR HY000: Variable 'innodb_deadlock_detect' is a GLOBAL variable and should be set with SET GLOBAL
set global innodb_deadlock_detect=1.1;
ERROR 42000: Incorrect argument type to variable 'innodb_deadlock_detect'
set global innodb_deadlock_detect=1e1;
ERROR 42000: Incorrect argument type to variable 'innodb_deadlock_detect'
set global innodb_deadlock_detect=2;
ERROR 42000: Variable 'innodb_deadlock_detect' can't be set to the value of '2'
NOTE: The following should fail with ER_WRONG_VALUE_FOR_VAR (BUG#50643)
set global innodb_deadlock_detect=-3;
select @@global.innodb_deadlock_detect;
@@global.innodb_deadlock_detect
1
select * from information_schema.global_variables where variable_name='innodb_deadlock_detect';
VARIABLE_NAME VARIABLE_VALUE
INNODB_DEADLOCK_DETECT ON
select * from information_schema.session_variables where variable_name='innodb_deadlock_detect';
VARIABLE_NAME VARIABLE_VALUE
INNODB_DEADLOCK_DETECT ON
set global innodb_deadlock_detect='AUTO';
ERROR 42000: Variable 'innodb_deadlock_detect' can't be set to the value of 'AUTO'
SET @@global.innodb_deadlock_detect = @start_global_value;
SELECT @@global.innodb_deadlock_detect;
@@global.innodb_deadlock_detect
1
@@ -0,0 +1,65 @@
--source include/have_innodb.inc

SET @start_global_value = @@global.innodb_deadlock_detect;
SELECT @start_global_value;

#
# exists as global only
#
--echo Valid values are 'ON' and 'OFF'
select @@global.innodb_deadlock_detect in (0, 1);
select @@global.innodb_deadlock_detect;
--error ER_INCORRECT_GLOBAL_LOCAL_VAR
select @@session.innodb_deadlock_detect;
show global variables like 'innodb_deadlock_detect';
show session variables like 'innodb_deadlock_detect';
select * from information_schema.global_variables where variable_name='innodb_deadlock_detect';
select * from information_schema.session_variables where variable_name='innodb_deadlock_detect';

#
# show that it's writable
#
set global innodb_deadlock_detect='OFF';
select @@global.innodb_deadlock_detect;
select * from information_schema.global_variables where variable_name='innodb_deadlock_detect';
select * from information_schema.session_variables where variable_name='innodb_deadlock_detect';
set @@global.innodb_deadlock_detect=1;
select @@global.innodb_deadlock_detect;
select * from information_schema.global_variables where variable_name='innodb_deadlock_detect';
select * from information_schema.session_variables where variable_name='innodb_deadlock_detect';
set global innodb_deadlock_detect=0;
select @@global.innodb_deadlock_detect;
select * from information_schema.global_variables where variable_name='innodb_deadlock_detect';
select * from information_schema.session_variables where variable_name='innodb_deadlock_detect';
set @@global.innodb_deadlock_detect='ON';
select @@global.innodb_deadlock_detect;
select * from information_schema.global_variables where variable_name='innodb_deadlock_detect';
select * from information_schema.session_variables where variable_name='innodb_deadlock_detect';
--error ER_GLOBAL_VARIABLE
set session innodb_deadlock_detect='OFF';
--error ER_GLOBAL_VARIABLE
set @@session.innodb_deadlock_detect='ON';

#
# incorrect types
#
--error ER_WRONG_TYPE_FOR_VAR
set global innodb_deadlock_detect=1.1;
--error ER_WRONG_TYPE_FOR_VAR
set global innodb_deadlock_detect=1e1;
--error ER_WRONG_VALUE_FOR_VAR
set global innodb_deadlock_detect=2;
--echo NOTE: The following should fail with ER_WRONG_VALUE_FOR_VAR (BUG#50643)
set global innodb_deadlock_detect=-3;
select @@global.innodb_deadlock_detect;
select * from information_schema.global_variables where variable_name='innodb_deadlock_detect';
select * from information_schema.session_variables where variable_name='innodb_deadlock_detect';
--error ER_WRONG_VALUE_FOR_VAR
set global innodb_deadlock_detect='AUTO';

#
# Cleanup
#

SET @@global.innodb_deadlock_detect = @start_global_value;
SELECT @@global.innodb_deadlock_detect;
@@ -16467,6 +16467,11 @@ static MYSQL_SYSVAR_ULONG(saved_page_number_debug,
NULL, innodb_save_page_no, 0, 0, UINT_MAX32, 0);
#endif /* UNIV_DEBUG */

static MYSQL_SYSVAR_BOOL(deadlock_detect, srv_deadlock_detect,
PLUGIN_VAR_NOCMDARG,
"Enableds deadlock detection checking.",
NULL, NULL, TRUE);

static struct st_mysql_sys_var* innobase_system_variables[]= {
MYSQL_SYSVAR(additional_mem_pool_size),
MYSQL_SYSVAR(api_trx_level),
@@ -16493,6 +16498,7 @@ static struct st_mysql_sys_var* innobase_system_variables[]= {
MYSQL_SYSVAR(compression_level),
MYSQL_SYSVAR(data_file_path),
MYSQL_SYSVAR(data_home_dir),
MYSQL_SYSVAR(deadlock_detect),
MYSQL_SYSVAR(doublewrite),
MYSQL_SYSVAR(api_enable_binlog),
MYSQL_SYSVAR(api_enable_mdl),
@@ -451,6 +451,9 @@ extern atomic_stat<ulint> srv_sec_rec_cluster_reads;
/** Number of times prefix optimization avoided triggering cluster lookup */
extern atomic_stat<ulint> srv_sec_rec_cluster_reads_avoided;

/** Perform deadlock detection check. */
extern my_bool srv_deadlock_detect;

/** Status variables to be passed to MySQL */
extern struct export_var_t export_vars;

@@ -1953,28 +1953,30 @@ lock_rec_enqueue_waiting(
its state can only be changed by this thread, which is
currently associated with the transaction. */

trx_mutex_exit(trx);
if (srv_deadlock_detect) {
trx_mutex_exit(trx);

victim_trx_id = lock_deadlock_check_and_resolve(lock, trx);
victim_trx_id = lock_deadlock_check_and_resolve(lock, trx);

trx_mutex_enter(trx);
trx_mutex_enter(trx);

if (victim_trx_id != 0) {
if (victim_trx_id != 0) {

ut_ad(victim_trx_id == trx->id);
ut_ad(victim_trx_id == trx->id);

lock_reset_lock_and_trx_wait(lock);
lock_rec_reset_nth_bit(lock, heap_no);
lock_reset_lock_and_trx_wait(lock);
lock_rec_reset_nth_bit(lock, heap_no);

return(DB_DEADLOCK);
return(DB_DEADLOCK);

} else if (trx->lock.wait_lock == NULL) {
} else if (trx->lock.wait_lock == NULL) {

/* If there was a deadlock but we chose another
transaction as a victim, it is possible that we
already have the lock now granted! */
/* If there was a deadlock but we chose another
transaction as a victim, it is possible that we
already have the lock now granted! */

return(DB_SUCCESS_LOCKED_REC);
return(DB_SUCCESS_LOCKED_REC);
}
}

trx->lock.que_state = TRX_QUE_LOCK_WAIT;
@@ -4300,26 +4302,28 @@ lock_table_enqueue_waiting(
its state can only be changed by this thread, which is
currently associated with the transaction. */

trx_mutex_exit(trx);
if (srv_deadlock_detect) {
trx_mutex_exit(trx);

victim_trx_id = lock_deadlock_check_and_resolve(lock, trx);
victim_trx_id = lock_deadlock_check_and_resolve(lock, trx);

trx_mutex_enter(trx);
trx_mutex_enter(trx);

if (victim_trx_id != 0) {
ut_ad(victim_trx_id == trx->id);
if (victim_trx_id != 0) {
ut_ad(victim_trx_id == trx->id);

/* The order here is important, we don't want to
lose the state of the lock before calling remove. */
lock_table_remove_low(lock);
lock_reset_lock_and_trx_wait(lock);
/* The order here is important, we don't want to
lose the state of the lock before calling remove. */
lock_table_remove_low(lock);
lock_reset_lock_and_trx_wait(lock);

return(DB_DEADLOCK);
} else if (trx->lock.wait_lock == NULL) {
/* Deadlock resolution chose another transaction as a victim,
and we accidentally got our lock granted! */
return(DB_DEADLOCK);
} else if (trx->lock.wait_lock == NULL) {
/* Deadlock resolution chose another transaction as a victim,
and we accidentally got our lock granted! */

return(DB_SUCCESS);
return(DB_SUCCESS);
}
}

trx->lock.que_state = TRX_QUE_LOCK_WAIT;
@@ -320,6 +320,9 @@ UNIV_INTERN ulong srv_force_recovery_crash;

UNIV_INTERN my_bool srv_print_all_deadlocks = FALSE;

/** Perform deadlock detection check. */
UNIV_INTERN my_bool srv_deadlock_detect = TRUE;

/** Enable INFORMATION_SCHEMA.innodb_cmp_per_index */
UNIV_INTERN my_bool srv_cmp_per_index_enabled = FALSE;

0 comments on commit d9e347d

Please sign in to comment.