Commit 3594adb1 authored by unknown's avatar unknown

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()
parent b7468a97
......@@ -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);
......
......@@ -693,10 +693,94 @@ run_again:
/* 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(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(0, prebuilt->table, LOCK_AUTO_INC, thr);
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);
......
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
......
-- 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);
}
/****************************************************************************
......
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