Commit a490b95b authored by marko@hundin.mysql.fi's avatar marko@hundin.mysql.fi

InnoDB: implement LOCK TABLE (Bug #3299)

parent 35579a2a
...@@ -381,7 +381,9 @@ lock_table( ...@@ -381,7 +381,9 @@ lock_table(
/* out: DB_SUCCESS, DB_LOCK_WAIT, /* out: DB_SUCCESS, DB_LOCK_WAIT,
DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */ DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */
ulint flags, /* in: if BTR_NO_LOCKING_FLAG bit is set, 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 */ dict_table_t* table, /* in: database table in dictionary cache */
ulint mode, /* in: lock mode */ ulint mode, /* in: lock mode */
que_thr_t* thr); /* in: query thread */ que_thr_t* thr); /* in: query thread */
...@@ -394,6 +396,14 @@ lock_is_on_table( ...@@ -394,6 +396,14 @@ lock_is_on_table(
/* out: TRUE if there are lock(s) */ /* out: TRUE if there are lock(s) */
dict_table_t* table); /* in: database table in dictionary cache */ 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 an auto-inc lock a transaction possibly has on a table.
Releases possible other transactions waiting for this lock. */ Releases possible other transactions waiting for this lock. */
...@@ -410,6 +420,14 @@ lock_release_off_kernel( ...@@ -410,6 +420,14 @@ lock_release_off_kernel(
/*====================*/ /*====================*/
trx_t* trx); /* in: transaction */ 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 Cancels a waiting lock request and releases possible other transactions
waiting behind it. */ waiting behind it. */
...@@ -536,6 +554,7 @@ extern lock_sys_t* lock_sys; ...@@ -536,6 +554,7 @@ extern lock_sys_t* lock_sys;
/* Lock types */ /* Lock types */
#define LOCK_TABLE 16 /* these type values should be so high that */ #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_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 #define LOCK_TYPE_MASK 0xF0UL /* mask used to extract lock type from the
type_mode field in a lock */ type_mode field in a lock */
/* Waiting lock flag */ /* Waiting lock flag */
......
...@@ -153,6 +153,22 @@ row_lock_table_autoinc_for_mysql( ...@@ -153,6 +153,22 @@ row_lock_table_autoinc_for_mysql(
row_prebuilt_t* prebuilt); /* in: prebuilt struct in the MySQL row_prebuilt_t* prebuilt); /* in: prebuilt struct in the MySQL
table handle */ 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. */ Does an insert for MySQL. */
int int
......
...@@ -421,6 +421,8 @@ struct trx_struct{ ...@@ -421,6 +421,8 @@ struct trx_struct{
lock_t* auto_inc_lock; /* possible auto-inc lock reserved by lock_t* auto_inc_lock; /* possible auto-inc lock reserved by
the transaction; note that it is also the transaction; note that it is also
in the lock list trx_locks */ 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) UT_LIST_NODE_T(trx_t)
trx_list; /* list of transactions */ trx_list; /* list of transactions */
UT_LIST_NODE_T(trx_t) UT_LIST_NODE_T(trx_t)
......
...@@ -2001,7 +2001,11 @@ lock_grant( ...@@ -2001,7 +2001,11 @@ lock_grant(
release it at the end of the SQL statement */ release it at the end of the SQL statement */
lock->trx->auto_inc_lock = lock; 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 #ifdef UNIV_DEBUG
if (lock_print_waits) { if (lock_print_waits) {
...@@ -2939,7 +2943,7 @@ retry: ...@@ -2939,7 +2943,7 @@ retry:
} }
if (ret == LOCK_VICTIM_IS_START) { 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; table = lock->un_member.tab_lock.table;
index = NULL; index = NULL;
} else { } else {
...@@ -3015,7 +3019,7 @@ lock_deadlock_recursive( ...@@ -3015,7 +3019,7 @@ lock_deadlock_recursive(
/* Look at the locks ahead of wait_lock in the lock queue */ /* Look at the locks ahead of wait_lock in the lock queue */
for (;;) { 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); lock = UT_LIST_GET_PREV(un_member.tab_lock.locks, lock);
} else { } else {
...@@ -3347,7 +3351,9 @@ lock_table( ...@@ -3347,7 +3351,9 @@ lock_table(
/* out: DB_SUCCESS, DB_LOCK_WAIT, /* out: DB_SUCCESS, DB_LOCK_WAIT,
DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */ DB_DEADLOCK, or DB_QUE_THR_SUSPENDED */
ulint flags, /* in: if BTR_NO_LOCKING_FLAG bit is set, 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 */ dict_table_t* table, /* in: database table in dictionary cache */
ulint mode, /* in: lock mode */ ulint mode, /* in: lock mode */
que_thr_t* thr) /* in: query thread */ que_thr_t* thr) /* in: query thread */
...@@ -3362,6 +3368,8 @@ lock_table( ...@@ -3362,6 +3368,8 @@ lock_table(
return(DB_SUCCESS); return(DB_SUCCESS);
} }
ut_ad(flags == 0 || flags == LOCK_TABLE_EXP);
trx = thr_get_trx(thr); trx = thr_get_trx(thr);
lock_mutex_enter_kernel(); lock_mutex_enter_kernel();
...@@ -3390,7 +3398,12 @@ lock_table( ...@@ -3390,7 +3398,12 @@ lock_table(
return(err); 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(); lock_mutex_exit_kernel();
...@@ -3471,7 +3484,8 @@ lock_table_dequeue( ...@@ -3471,7 +3484,8 @@ lock_table_dequeue(
#ifdef UNIV_SYNC_DEBUG #ifdef UNIV_SYNC_DEBUG
ut_ad(mutex_own(&kernel_mutex)); ut_ad(mutex_own(&kernel_mutex));
#endif /* UNIV_SYNC_DEBUG */ #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); lock = UT_LIST_GET_NEXT(un_member.tab_lock.locks, in_lock);
...@@ -3495,6 +3509,22 @@ lock_table_dequeue( ...@@ -3495,6 +3509,22 @@ lock_table_dequeue(
/*=========================== LOCK RELEASE ==============================*/ /*=========================== 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 an auto-inc lock a transaction possibly has on a table.
Releases possible other transactions waiting for this lock. */ Releases possible other transactions waiting for this lock. */
...@@ -3542,7 +3572,7 @@ lock_release_off_kernel( ...@@ -3542,7 +3572,7 @@ lock_release_off_kernel(
lock_rec_dequeue_from_page(lock); lock_rec_dequeue_from_page(lock);
} else { } else {
ut_ad(lock_get_type(lock) == LOCK_TABLE); ut_ad(lock_get_type(lock) & LOCK_TABLE);
if (lock_get_mode(lock) != LOCK_IS if (lock_get_mode(lock) != LOCK_IS
&& (trx->insert_undo || trx->update_undo)) { && (trx->insert_undo || trx->update_undo)) {
...@@ -3558,6 +3588,11 @@ lock_release_off_kernel( ...@@ -3558,6 +3588,11 @@ lock_release_off_kernel(
} }
lock_table_dequeue(lock); 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) { if (count == LOCK_RELEASE_KERNEL_INTERVAL) {
...@@ -3577,6 +3612,73 @@ lock_release_off_kernel( ...@@ -3577,6 +3612,73 @@ lock_release_off_kernel(
mem_heap_empty(trx->lock_heap); mem_heap_empty(trx->lock_heap);
ut_a(trx->auto_inc_lock == NULL); 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( ...@@ -3596,7 +3698,7 @@ lock_cancel_waiting_and_release(
lock_rec_dequeue_from_page(lock); lock_rec_dequeue_from_page(lock);
} else { } else {
ut_ad(lock_get_type(lock) == LOCK_TABLE); ut_ad(lock_get_type(lock) & LOCK_TABLE);
lock_table_dequeue(lock); lock_table_dequeue(lock);
} }
...@@ -3637,7 +3739,7 @@ lock_reset_all_on_table_for_trx( ...@@ -3637,7 +3739,7 @@ lock_reset_all_on_table_for_trx(
ut_a(!lock_get_wait(lock)); ut_a(!lock_get_wait(lock));
lock_rec_discard(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) { && lock->un_member.tab_lock.table == table) {
ut_a(!lock_get_wait(lock)); ut_a(!lock_get_wait(lock));
...@@ -3689,8 +3791,12 @@ lock_table_print( ...@@ -3689,8 +3791,12 @@ lock_table_print(
#ifdef UNIV_SYNC_DEBUG #ifdef UNIV_SYNC_DEBUG
ut_ad(mutex_own(&kernel_mutex)); ut_ad(mutex_own(&kernel_mutex));
#endif /* UNIV_SYNC_DEBUG */ #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); fputs("TABLE LOCK table ", file);
ut_print_name(file, lock->un_member.tab_lock.table->name); ut_print_name(file, lock->un_member.tab_lock.table->name);
fprintf(file, " trx id %lu %lu", fprintf(file, " trx id %lu %lu",
...@@ -4009,7 +4115,7 @@ loop: ...@@ -4009,7 +4115,7 @@ loop:
lock_rec_print(file, lock); lock_rec_print(file, lock);
} else { } else {
ut_ad(lock_get_type(lock) == LOCK_TABLE); ut_ad(lock_get_type(lock) & LOCK_TABLE);
lock_table_print(file, lock); lock_table_print(file, lock);
} }
...@@ -4290,7 +4396,7 @@ lock_validate(void) ...@@ -4290,7 +4396,7 @@ lock_validate(void)
lock = UT_LIST_GET_FIRST(trx->trx_locks); lock = UT_LIST_GET_FIRST(trx->trx_locks);
while (lock) { while (lock) {
if (lock_get_type(lock) == LOCK_TABLE) { if (lock_get_type(lock) & LOCK_TABLE) {
lock_table_queue_validate( lock_table_queue_validate(
lock->un_member.tab_lock.table); lock->un_member.tab_lock.table);
......
...@@ -693,10 +693,94 @@ run_again: ...@@ -693,10 +693,94 @@ run_again:
/* It may be that the current session has not yet started /* It may be that the current session has not yet started
its transaction, or it has been committed: */ 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); 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; trx->error_state = err;
......
...@@ -151,6 +151,7 @@ trx_create( ...@@ -151,6 +151,7 @@ trx_create(
trx->n_tickets_to_enter_innodb = 0; trx->n_tickets_to_enter_innodb = 0;
trx->auto_inc_lock = NULL; trx->auto_inc_lock = NULL;
trx->n_tables_locked = 0;
trx->read_view_heap = mem_heap_create(256); trx->read_view_heap = mem_heap_create(256);
trx->read_view = NULL; trx->read_view = NULL;
...@@ -278,6 +279,7 @@ trx_free( ...@@ -278,6 +279,7 @@ trx_free(
ut_a(!trx->has_search_latch); ut_a(!trx->has_search_latch);
ut_a(!trx->auto_inc_lock); ut_a(!trx->auto_inc_lock);
ut_a(!trx->n_tables_locked);
ut_a(trx->dict_operation_lock_mode == 0); 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 ...@@ -431,7 +431,7 @@ Duplicate entry 'test2' for key 2
select * from t1; select * from t1;
id ggid email passwd id ggid email passwd
1 this will work 1 this will work
3 test2 this will work 4 test2 this will work
select * from t1 where id=1; select * from t1 where id=1;
id ggid email passwd id ggid email passwd
1 this will work 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. */ ...@@ -4494,12 +4494,11 @@ the SQL statement in case of an error. */
int int
ha_innobase::external_lock( ha_innobase::external_lock(
/*=======================*/ /*=======================*/
/* out: 0 or error code */ /* out: 0 */
THD* thd, /* in: handle to the user thread */ THD* thd, /* in: handle to the user thread */
int lock_type) /* in: lock type */ int lock_type) /* in: lock type */
{ {
row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt; row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
int error = 0;
trx_t* trx; trx_t* trx;
DBUG_ENTER("ha_innobase::external_lock"); DBUG_ENTER("ha_innobase::external_lock");
...@@ -4554,11 +4553,21 @@ ha_innobase::external_lock( ...@@ -4554,11 +4553,21 @@ ha_innobase::external_lock(
} }
if (prebuilt->select_lock_type != LOCK_NONE) { 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++; trx->mysql_n_tables_locked++;
} }
DBUG_RETURN(error); DBUG_RETURN(0);
} }
/* MySQL is releasing a table lock */ /* MySQL is releasing a table lock */
...@@ -4566,6 +4575,9 @@ ha_innobase::external_lock( ...@@ -4566,6 +4575,9 @@ ha_innobase::external_lock(
trx->n_mysql_tables_in_use--; trx->n_mysql_tables_in_use--;
prebuilt->mysql_has_locked = FALSE; prebuilt->mysql_has_locked = FALSE;
auto_inc_counter_for_this_stat = 0; 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 /* If the MySQL lock count drops to zero we know that the current SQL
statement has ended */ statement has ended */
...@@ -4598,7 +4610,7 @@ ha_innobase::external_lock( ...@@ -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