mirror of
https://github.com/MariaDB/server.git
synced 2025-07-29 05:21:33 +03:00
InnoDB: implement LOCK TABLE (Bug #3299)
innobase/include/lock0lock.h: Add lock_table_unlock() and lock_release_tables_off_kernel() Add LOCK_TABLE_EXP innobase/include/row0mysql.h: Add row_unlock_table_for_mysql() and row_lock_table_for_mysql() innobase/include/trx0trx.h: Add n_tables_locked innobase/lock/lock0lock.c: Add LOCK_TABLE_EXP for explicit LOCK TABLE commands Add lock_table_unlock() Add lock_release_tables_off_kernel() innobase/row/row0mysql.c: Add row_unlock_table_for_mysql() and row_lock_table_for_mysql() innobase/trx/trx0trx.c: Add n_tables_locked mysql-test/r/innodb.result: Updated handling of auto_inc columns sql/ha_innodb.cc: Call row_lock_table_for_mysql() and row_unlock_table_for_mysql()
This commit is contained in:
@ -381,7 +381,9 @@ lock_table(
|
||||
/* out: DB_SUCCESS, DB_LOCK_WAIT,
|
||||
DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */
|
||||
ulint flags, /* in: if BTR_NO_LOCKING_FLAG bit is set,
|
||||
does nothing */
|
||||
does nothing;
|
||||
if LOCK_TABLE_EXP bits are set,
|
||||
creates an explicit table lock */
|
||||
dict_table_t* table, /* in: database table in dictionary cache */
|
||||
ulint mode, /* in: lock mode */
|
||||
que_thr_t* thr); /* in: query thread */
|
||||
@ -394,6 +396,14 @@ lock_is_on_table(
|
||||
/* out: TRUE if there are lock(s) */
|
||||
dict_table_t* table); /* in: database table in dictionary cache */
|
||||
/*************************************************************************
|
||||
Releases a table lock.
|
||||
Releases possible other transactions waiting for this lock. */
|
||||
|
||||
void
|
||||
lock_table_unlock(
|
||||
/*==============*/
|
||||
lock_t* lock); /* in: lock */
|
||||
/*************************************************************************
|
||||
Releases an auto-inc lock a transaction possibly has on a table.
|
||||
Releases possible other transactions waiting for this lock. */
|
||||
|
||||
@ -410,6 +420,14 @@ lock_release_off_kernel(
|
||||
/*====================*/
|
||||
trx_t* trx); /* in: transaction */
|
||||
/*************************************************************************
|
||||
Releases table locks, and releases possible other transactions waiting
|
||||
because of these locks. */
|
||||
|
||||
void
|
||||
lock_release_tables_off_kernel(
|
||||
/*===========================*/
|
||||
trx_t* trx); /* in: transaction */
|
||||
/*************************************************************************
|
||||
Cancels a waiting lock request and releases possible other transactions
|
||||
waiting behind it. */
|
||||
|
||||
@ -536,6 +554,7 @@ extern lock_sys_t* lock_sys;
|
||||
/* Lock types */
|
||||
#define LOCK_TABLE 16 /* these type values should be so high that */
|
||||
#define LOCK_REC 32 /* they can be ORed to the lock mode */
|
||||
#define LOCK_TABLE_EXP 80 /* explicit table lock */
|
||||
#define LOCK_TYPE_MASK 0xF0UL /* mask used to extract lock type from the
|
||||
type_mode field in a lock */
|
||||
/* Waiting lock flag */
|
||||
|
@ -153,6 +153,22 @@ row_lock_table_autoinc_for_mysql(
|
||||
row_prebuilt_t* prebuilt); /* in: prebuilt struct in the MySQL
|
||||
table handle */
|
||||
/*************************************************************************
|
||||
Unlocks a table lock possibly reserved by trx. */
|
||||
|
||||
void
|
||||
row_unlock_table_for_mysql(
|
||||
/*=======================*/
|
||||
trx_t* trx); /* in: transaction */
|
||||
/*************************************************************************
|
||||
Sets a table lock on the table mentioned in prebuilt. */
|
||||
|
||||
int
|
||||
row_lock_table_for_mysql(
|
||||
/*=====================*/
|
||||
/* out: error code or DB_SUCCESS */
|
||||
row_prebuilt_t* prebuilt); /* in: prebuilt struct in the MySQL
|
||||
table handle */
|
||||
/*************************************************************************
|
||||
Does an insert for MySQL. */
|
||||
|
||||
int
|
||||
|
@ -421,6 +421,8 @@ struct trx_struct{
|
||||
lock_t* auto_inc_lock; /* possible auto-inc lock reserved by
|
||||
the transaction; note that it is also
|
||||
in the lock list trx_locks */
|
||||
ulint n_tables_locked;/* number of table locks reserved by
|
||||
the transaction, stored in trx_locks */
|
||||
UT_LIST_NODE_T(trx_t)
|
||||
trx_list; /* list of transactions */
|
||||
UT_LIST_NODE_T(trx_t)
|
||||
|
@ -2001,7 +2001,11 @@ lock_grant(
|
||||
release it at the end of the SQL statement */
|
||||
|
||||
lock->trx->auto_inc_lock = lock;
|
||||
}
|
||||
} else if (lock_get_type(lock) == LOCK_TABLE_EXP) {
|
||||
ut_ad(lock_get_mode(lock) == LOCK_S
|
||||
|| lock_get_mode(lock) == LOCK_X);
|
||||
lock->trx->n_tables_locked++;
|
||||
}
|
||||
|
||||
#ifdef UNIV_DEBUG
|
||||
if (lock_print_waits) {
|
||||
@ -2939,7 +2943,7 @@ retry:
|
||||
}
|
||||
|
||||
if (ret == LOCK_VICTIM_IS_START) {
|
||||
if (lock_get_type(lock) == LOCK_TABLE) {
|
||||
if (lock_get_type(lock) & LOCK_TABLE) {
|
||||
table = lock->un_member.tab_lock.table;
|
||||
index = NULL;
|
||||
} else {
|
||||
@ -3015,7 +3019,7 @@ lock_deadlock_recursive(
|
||||
/* Look at the locks ahead of wait_lock in the lock queue */
|
||||
|
||||
for (;;) {
|
||||
if (lock_get_type(lock) == LOCK_TABLE) {
|
||||
if (lock_get_type(lock) & LOCK_TABLE) {
|
||||
|
||||
lock = UT_LIST_GET_PREV(un_member.tab_lock.locks, lock);
|
||||
} else {
|
||||
@ -3347,7 +3351,9 @@ lock_table(
|
||||
/* out: DB_SUCCESS, DB_LOCK_WAIT,
|
||||
DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */
|
||||
ulint flags, /* in: if BTR_NO_LOCKING_FLAG bit is set,
|
||||
does nothing */
|
||||
does nothing;
|
||||
if LOCK_TABLE_EXP bits are set,
|
||||
creates an explicit table lock */
|
||||
dict_table_t* table, /* in: database table in dictionary cache */
|
||||
ulint mode, /* in: lock mode */
|
||||
que_thr_t* thr) /* in: query thread */
|
||||
@ -3362,6 +3368,8 @@ lock_table(
|
||||
return(DB_SUCCESS);
|
||||
}
|
||||
|
||||
ut_ad(flags == 0 || flags == LOCK_TABLE_EXP);
|
||||
|
||||
trx = thr_get_trx(thr);
|
||||
|
||||
lock_mutex_enter_kernel();
|
||||
@ -3390,7 +3398,12 @@ lock_table(
|
||||
return(err);
|
||||
}
|
||||
|
||||
lock_table_create(table, mode, trx);
|
||||
lock_table_create(table, mode | flags, trx);
|
||||
|
||||
if (flags) {
|
||||
ut_ad(mode == LOCK_S || mode == LOCK_X);
|
||||
trx->n_tables_locked++;
|
||||
}
|
||||
|
||||
lock_mutex_exit_kernel();
|
||||
|
||||
@ -3471,7 +3484,8 @@ lock_table_dequeue(
|
||||
#ifdef UNIV_SYNC_DEBUG
|
||||
ut_ad(mutex_own(&kernel_mutex));
|
||||
#endif /* UNIV_SYNC_DEBUG */
|
||||
ut_ad(lock_get_type(in_lock) == LOCK_TABLE);
|
||||
ut_ad(lock_get_type(in_lock) == LOCK_TABLE ||
|
||||
lock_get_type(in_lock) == LOCK_TABLE_EXP);
|
||||
|
||||
lock = UT_LIST_GET_NEXT(un_member.tab_lock.locks, in_lock);
|
||||
|
||||
@ -3495,6 +3509,22 @@ lock_table_dequeue(
|
||||
|
||||
/*=========================== LOCK RELEASE ==============================*/
|
||||
|
||||
/*************************************************************************
|
||||
Releases a table lock.
|
||||
Releases possible other transactions waiting for this lock. */
|
||||
|
||||
void
|
||||
lock_table_unlock(
|
||||
/*==============*/
|
||||
lock_t* lock) /* in: lock */
|
||||
{
|
||||
mutex_enter(&kernel_mutex);
|
||||
|
||||
lock_table_dequeue(lock);
|
||||
|
||||
mutex_exit(&kernel_mutex);
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
Releases an auto-inc lock a transaction possibly has on a table.
|
||||
Releases possible other transactions waiting for this lock. */
|
||||
@ -3542,7 +3572,7 @@ lock_release_off_kernel(
|
||||
|
||||
lock_rec_dequeue_from_page(lock);
|
||||
} else {
|
||||
ut_ad(lock_get_type(lock) == LOCK_TABLE);
|
||||
ut_ad(lock_get_type(lock) & LOCK_TABLE);
|
||||
|
||||
if (lock_get_mode(lock) != LOCK_IS
|
||||
&& (trx->insert_undo || trx->update_undo)) {
|
||||
@ -3558,6 +3588,11 @@ lock_release_off_kernel(
|
||||
}
|
||||
|
||||
lock_table_dequeue(lock);
|
||||
if (lock_get_type(lock) == LOCK_TABLE_EXP) {
|
||||
ut_ad(lock_get_mode(lock) == LOCK_S
|
||||
|| lock_get_mode(lock) == LOCK_X);
|
||||
trx->n_tables_locked--;
|
||||
}
|
||||
}
|
||||
|
||||
if (count == LOCK_RELEASE_KERNEL_INTERVAL) {
|
||||
@ -3577,6 +3612,73 @@ lock_release_off_kernel(
|
||||
mem_heap_empty(trx->lock_heap);
|
||||
|
||||
ut_a(trx->auto_inc_lock == NULL);
|
||||
ut_a(trx->n_tables_locked == 0);
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
Releases table locks, and releases possible other transactions waiting
|
||||
because of these locks. */
|
||||
|
||||
void
|
||||
lock_release_tables_off_kernel(
|
||||
/*===========================*/
|
||||
trx_t* trx) /* in: transaction */
|
||||
{
|
||||
dict_table_t* table;
|
||||
ulint count;
|
||||
lock_t* lock;
|
||||
|
||||
#ifdef UNIV_SYNC_DEBUG
|
||||
ut_ad(mutex_own(&kernel_mutex));
|
||||
#endif /* UNIV_SYNC_DEBUG */
|
||||
|
||||
lock = UT_LIST_GET_LAST(trx->trx_locks);
|
||||
|
||||
count = 0;
|
||||
|
||||
while (lock != NULL) {
|
||||
|
||||
count++;
|
||||
|
||||
if (lock_get_type(lock) == LOCK_TABLE_EXP) {
|
||||
ut_ad(lock_get_mode(lock) == LOCK_S
|
||||
|| lock_get_mode(lock) == LOCK_X);
|
||||
if (trx->insert_undo || trx->update_undo) {
|
||||
|
||||
/* The trx may have modified the table.
|
||||
We block the use of the MySQL query
|
||||
cache for all currently active
|
||||
transactions. */
|
||||
|
||||
table = lock->un_member.tab_lock.table;
|
||||
|
||||
table->query_cache_inv_trx_id =
|
||||
trx_sys->max_trx_id;
|
||||
}
|
||||
|
||||
lock_table_dequeue(lock);
|
||||
trx->n_tables_locked--;
|
||||
lock = UT_LIST_GET_LAST(trx->trx_locks);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (count == LOCK_RELEASE_KERNEL_INTERVAL) {
|
||||
/* Release the kernel mutex for a while, so that we
|
||||
do not monopolize it */
|
||||
|
||||
lock_mutex_exit_kernel();
|
||||
|
||||
lock_mutex_enter_kernel();
|
||||
|
||||
count = 0;
|
||||
}
|
||||
|
||||
lock = UT_LIST_GET_PREV(trx_locks, lock);
|
||||
}
|
||||
|
||||
mem_heap_empty(trx->lock_heap);
|
||||
|
||||
ut_a(trx->n_tables_locked == 0);
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
@ -3596,7 +3698,7 @@ lock_cancel_waiting_and_release(
|
||||
|
||||
lock_rec_dequeue_from_page(lock);
|
||||
} else {
|
||||
ut_ad(lock_get_type(lock) == LOCK_TABLE);
|
||||
ut_ad(lock_get_type(lock) & LOCK_TABLE);
|
||||
|
||||
lock_table_dequeue(lock);
|
||||
}
|
||||
@ -3637,7 +3739,7 @@ lock_reset_all_on_table_for_trx(
|
||||
ut_a(!lock_get_wait(lock));
|
||||
|
||||
lock_rec_discard(lock);
|
||||
} else if (lock_get_type(lock) == LOCK_TABLE
|
||||
} else if (lock_get_type(lock) & LOCK_TABLE
|
||||
&& lock->un_member.tab_lock.table == table) {
|
||||
|
||||
ut_a(!lock_get_wait(lock));
|
||||
@ -3689,8 +3791,12 @@ lock_table_print(
|
||||
#ifdef UNIV_SYNC_DEBUG
|
||||
ut_ad(mutex_own(&kernel_mutex));
|
||||
#endif /* UNIV_SYNC_DEBUG */
|
||||
ut_a(lock_get_type(lock) == LOCK_TABLE);
|
||||
ut_a(lock_get_type(lock) == LOCK_TABLE ||
|
||||
lock_get_type(lock) == LOCK_TABLE_EXP);
|
||||
|
||||
if (lock_get_type(lock) == LOCK_TABLE_EXP) {
|
||||
fputs("EXPLICIT ", file);
|
||||
}
|
||||
fputs("TABLE LOCK table ", file);
|
||||
ut_print_name(file, lock->un_member.tab_lock.table->name);
|
||||
fprintf(file, " trx id %lu %lu",
|
||||
@ -4009,7 +4115,7 @@ loop:
|
||||
|
||||
lock_rec_print(file, lock);
|
||||
} else {
|
||||
ut_ad(lock_get_type(lock) == LOCK_TABLE);
|
||||
ut_ad(lock_get_type(lock) & LOCK_TABLE);
|
||||
|
||||
lock_table_print(file, lock);
|
||||
}
|
||||
@ -4290,7 +4396,7 @@ lock_validate(void)
|
||||
lock = UT_LIST_GET_FIRST(trx->trx_locks);
|
||||
|
||||
while (lock) {
|
||||
if (lock_get_type(lock) == LOCK_TABLE) {
|
||||
if (lock_get_type(lock) & LOCK_TABLE) {
|
||||
|
||||
lock_table_queue_validate(
|
||||
lock->un_member.tab_lock.table);
|
||||
|
@ -696,7 +696,91 @@ run_again:
|
||||
|
||||
trx_start_if_not_started(trx);
|
||||
|
||||
err = lock_table(0, prebuilt->table, LOCK_AUTO_INC, thr);
|
||||
err = lock_table(0, prebuilt->table, prebuilt->select_lock_type, thr);
|
||||
|
||||
trx->error_state = err;
|
||||
|
||||
if (err != DB_SUCCESS) {
|
||||
que_thr_stop_for_mysql(thr);
|
||||
|
||||
was_lock_wait = row_mysql_handle_errors(&err, trx, thr, NULL);
|
||||
|
||||
if (was_lock_wait) {
|
||||
goto run_again;
|
||||
}
|
||||
|
||||
trx->op_info = (char *) "";
|
||||
|
||||
return(err);
|
||||
}
|
||||
|
||||
que_thr_stop_for_mysql_no_error(thr, trx);
|
||||
|
||||
trx->op_info = (char *) "";
|
||||
|
||||
return((int) err);
|
||||
}
|
||||
|
||||
/*************************************************************************
|
||||
Unlocks a table lock possibly reserved by trx. */
|
||||
|
||||
void
|
||||
row_unlock_table_for_mysql(
|
||||
/*=======================*/
|
||||
trx_t* trx) /* in: transaction */
|
||||
{
|
||||
if (!trx->n_tables_locked) {
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
mutex_enter(&kernel_mutex);
|
||||
lock_release_tables_off_kernel(trx);
|
||||
mutex_exit(&kernel_mutex);
|
||||
}
|
||||
/*************************************************************************
|
||||
Sets a table lock on the table mentioned in prebuilt. */
|
||||
|
||||
int
|
||||
row_lock_table_for_mysql(
|
||||
/*=====================*/
|
||||
/* out: error code or DB_SUCCESS */
|
||||
row_prebuilt_t* prebuilt) /* in: prebuilt struct in the MySQL
|
||||
table handle */
|
||||
{
|
||||
trx_t* trx = prebuilt->trx;
|
||||
que_thr_t* thr;
|
||||
ulint err;
|
||||
ibool was_lock_wait;
|
||||
|
||||
ut_ad(trx);
|
||||
ut_ad(trx->mysql_thread_id == os_thread_get_curr_id());
|
||||
|
||||
trx->op_info = (char *) "setting table lock";
|
||||
|
||||
if (prebuilt->sel_graph == NULL) {
|
||||
/* Build a dummy select query graph */
|
||||
row_prebuild_sel_graph(prebuilt);
|
||||
}
|
||||
|
||||
/* We use the select query graph as the dummy graph needed
|
||||
in the lock module call */
|
||||
|
||||
thr = que_fork_get_first_thr(prebuilt->sel_graph);
|
||||
|
||||
que_thr_move_to_run_state_for_mysql(thr, trx);
|
||||
|
||||
run_again:
|
||||
thr->run_node = thr;
|
||||
thr->prev_node = thr->common.parent;
|
||||
|
||||
/* It may be that the current session has not yet started
|
||||
its transaction, or it has been committed: */
|
||||
|
||||
trx_start_if_not_started(trx);
|
||||
|
||||
err = lock_table(LOCK_TABLE_EXP, prebuilt->table,
|
||||
prebuilt->select_lock_type, thr);
|
||||
|
||||
trx->error_state = err;
|
||||
|
||||
|
@ -151,6 +151,7 @@ trx_create(
|
||||
trx->n_tickets_to_enter_innodb = 0;
|
||||
|
||||
trx->auto_inc_lock = NULL;
|
||||
trx->n_tables_locked = 0;
|
||||
|
||||
trx->read_view_heap = mem_heap_create(256);
|
||||
trx->read_view = NULL;
|
||||
@ -278,6 +279,7 @@ trx_free(
|
||||
|
||||
ut_a(!trx->has_search_latch);
|
||||
ut_a(!trx->auto_inc_lock);
|
||||
ut_a(!trx->n_tables_locked);
|
||||
|
||||
ut_a(trx->dict_operation_lock_mode == 0);
|
||||
|
||||
|
22
mysql-test/r/innodb-lock.result
Normal file
22
mysql-test/r/innodb-lock.result
Normal file
@ -0,0 +1,22 @@
|
||||
drop table if exists t1;
|
||||
create table t1 (id integer, x integer) engine=INNODB;
|
||||
insert into t1 values(0, 0);
|
||||
set autocommit=0;
|
||||
SELECT * from t1 where id = 0 FOR UPDATE;
|
||||
id x
|
||||
0 0
|
||||
set autocommit=0;
|
||||
lock table t1 write;
|
||||
update t1 set x=1 where id = 0;
|
||||
select * from t1;
|
||||
id x
|
||||
0 1
|
||||
commit;
|
||||
update t1 set x=2 where id = 0;
|
||||
commit;
|
||||
unlock tables;
|
||||
select * from t1;
|
||||
id x
|
||||
0 2
|
||||
commit;
|
||||
drop table t1;
|
@ -431,7 +431,7 @@ Duplicate entry 'test2' for key 2
|
||||
select * from t1;
|
||||
id ggid email passwd
|
||||
1 this will work
|
||||
3 test2 this will work
|
||||
4 test2 this will work
|
||||
select * from t1 where id=1;
|
||||
id ggid email passwd
|
||||
1 this will work
|
||||
|
40
mysql-test/t/innodb-lock.test
Normal file
40
mysql-test/t/innodb-lock.test
Normal file
@ -0,0 +1,40 @@
|
||||
-- source include/have_innodb.inc
|
||||
|
||||
connect (con1,localhost,root,,);
|
||||
connect (con2,localhost,root,,);
|
||||
drop table if exists t1;
|
||||
|
||||
#
|
||||
# Testing of explicit table locks
|
||||
#
|
||||
|
||||
connection con1;
|
||||
create table t1 (id integer, x integer) engine=INNODB;
|
||||
insert into t1 values(0, 0);
|
||||
set autocommit=0;
|
||||
SELECT * from t1 where id = 0 FOR UPDATE;
|
||||
|
||||
connection con2;
|
||||
set autocommit=0;
|
||||
|
||||
# The following statement should hang because con1 is locking the page
|
||||
--send
|
||||
lock table t1 write;
|
||||
--sleep 2;
|
||||
|
||||
connection con1;
|
||||
update t1 set x=1 where id = 0;
|
||||
select * from t1;
|
||||
commit;
|
||||
|
||||
connection con2;
|
||||
reap;
|
||||
update t1 set x=2 where id = 0;
|
||||
commit;
|
||||
unlock tables;
|
||||
|
||||
connection con1;
|
||||
select * from t1;
|
||||
commit;
|
||||
|
||||
drop table t1;
|
@ -4494,12 +4494,11 @@ the SQL statement in case of an error. */
|
||||
int
|
||||
ha_innobase::external_lock(
|
||||
/*=======================*/
|
||||
/* out: 0 or error code */
|
||||
/* out: 0 */
|
||||
THD* thd, /* in: handle to the user thread */
|
||||
int lock_type) /* in: lock type */
|
||||
{
|
||||
row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
|
||||
int error = 0;
|
||||
trx_t* trx;
|
||||
|
||||
DBUG_ENTER("ha_innobase::external_lock");
|
||||
@ -4554,11 +4553,21 @@ ha_innobase::external_lock(
|
||||
}
|
||||
|
||||
if (prebuilt->select_lock_type != LOCK_NONE) {
|
||||
if (thd->in_lock_tables) {
|
||||
ulint error;
|
||||
error = row_lock_table_for_mysql(prebuilt);
|
||||
|
||||
if (error != DB_SUCCESS) {
|
||||
error = convert_error_code_to_mysql(
|
||||
error, user_thd);
|
||||
DBUG_RETURN(error);
|
||||
}
|
||||
}
|
||||
|
||||
trx->mysql_n_tables_locked++;
|
||||
}
|
||||
|
||||
DBUG_RETURN(error);
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
/* MySQL is releasing a table lock */
|
||||
@ -4566,6 +4575,9 @@ ha_innobase::external_lock(
|
||||
trx->n_mysql_tables_in_use--;
|
||||
prebuilt->mysql_has_locked = FALSE;
|
||||
auto_inc_counter_for_this_stat = 0;
|
||||
if (trx->n_tables_locked) {
|
||||
row_unlock_table_for_mysql(trx);
|
||||
}
|
||||
|
||||
/* If the MySQL lock count drops to zero we know that the current SQL
|
||||
statement has ended */
|
||||
@ -4598,7 +4610,7 @@ ha_innobase::external_lock(
|
||||
}
|
||||
}
|
||||
|
||||
DBUG_RETURN(error);
|
||||
DBUG_RETURN(0);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
|
Reference in New Issue
Block a user