diff --git a/include/thr_lock.h b/include/thr_lock.h index 1ea2413..4bdaab3 100644 --- a/include/thr_lock.h +++ b/include/thr_lock.h @@ -72,6 +72,16 @@ /* Abort new lock request with an error */ TL_WRITE_ONLY}; +/* X-lock types : these are complements to TL_WRITE + TL_WRITE + TL_X_LOCK_SKIP_LOCKED is for SELECT ... FOR UPDATE SKIP LOCKED. + TL_WRITE + TL_X_LOCK_NOWAIT is for SELECT ... FOR UPDATE NOWAIT. + TL_X_LOCK_REGULAR is the default value, which means the TL_WRITE is regular. + It will be ignored if X-lock types combine with other lock types. +*/ +enum thr_x_lock_type { TL_X_LOCK_REGULAR = 0, + TL_X_LOCK_SKIP_LOCKED, + TL_X_LOCK_TYPE_COUNT }; + enum enum_thr_lock_result { THR_LOCK_SUCCESS= 0, THR_LOCK_ABORTED= 1, THR_LOCK_WAIT_TIMEOUT= 2, THR_LOCK_DEADLOCK= 3 }; diff --git a/mysql-test/suite/innodb/r/innodb_lock_skip_locked_nowait.result b/mysql-test/suite/innodb/r/innodb_lock_skip_locked_nowait.result new file mode 100644 index 0000000..211060d --- /dev/null +++ b/mysql-test/suite/innodb/r/innodb_lock_skip_locked_nowait.result @@ -0,0 +1,177 @@ +drop table if exists t0, t1, t2, t3; +create table t0 (id integer, x integer) engine=INNODB; +insert into t0 values(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +create table t1 (id integer primary key, x integer) engine=INNODB; +insert into t1 values(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +create table t2 (id integer primary key, x integer, unique key x(x)) engine=INNODB; +insert into t2 values(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +create table t3 (id integer primary key, x integer, key x(x)) engine=INNODB; +insert into t3 values(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); +### (0.1) Without pirmary key: Locks on all rows +set autocommit=0; +SELECT * from t0 where id = 1 FOR UPDATE; +id x +1 1 +set autocommit=0; +lock will skip +SELECT * from t0 where id >= 0 FOR UPDATE SKIP LOCKED; +id x +lock will skip +SELECT * from t0 order by id FOR UPDATE SKIP LOCKED; +id x +lock will not wait, expecting error +SELECT * from t0 where id = 1 FOR UPDATE NOWAIT; +ERROR HY000: Failed to lock a record and didn't wait +commit; +commit; +### (0.2) Without pirmary key: Locks on some rows with 'limit x' +set autocommit=0; +SELECT * from t0 where id = 1 limit 1 FOR UPDATE; +id x +1 1 +set autocommit=0; +lock will skip +SELECT * from t0 where id >= 0 FOR UPDATE SKIP LOCKED; +id x +2 2 +3 3 +4 4 +5 5 +lock will skip +SELECT * from t0 order by id FOR UPDATE SKIP LOCKED; +id x +2 2 +3 3 +4 4 +5 5 +lock will not wait, expecting error +SELECT * from t0 where id = 1 FOR UPDATE NOWAIT; +ERROR HY000: Failed to lock a record and didn't wait +commit; +commit; +### (1) With pirmary key: Locks only on the record (covering index) +set autocommit=0; +SELECT * from t1 where id = 1 FOR UPDATE; +id x +1 1 +set autocommit=0; +lock will skip +SELECT * from t1 where id >= 0 and id <= 3 FOR UPDATE SKIP LOCKED; +id x +0 0 +2 2 +3 3 +set autocommit=0; +lock will skip +SELECT * from t1 where id >= 0 FOR UPDATE SKIP LOCKED; +id x +5 5 +lock will skip +SELECT * from t1 order by id FOR UPDATE SKIP LOCKED; +id x +5 5 +lock will not wait, expecting error +SELECT * from t1 where id = 1 FOR UPDATE NOWAIT; +ERROR HY000: Failed to lock a record and didn't wait +commit; +commit; +commit; +### (2) With unique secondary key: Locks on the unique secondary key (covering index) +set autocommit=0; +SELECT x from t2 where x = 1 FOR UPDATE; +x +1 +set autocommit=0; +lock will skip +SELECT x from t2 where x >= 0 and x <= 3 FOR UPDATE SKIP LOCKED; +x +0 +2 +3 +set autocommit=0; +lock will skip +SELECT x from t2 where x >= 0 FOR UPDATE SKIP LOCKED; +x +5 +lock will skip +SELECT x from t2 order by x FOR UPDATE SKIP LOCKED; +x +5 +lock will not wait, expecting error +SELECT x from t2 where x = 1 FOR UPDATE NOWAIT; +ERROR HY000: Failed to lock a record and didn't wait +commit; +commit; +commit; +### (3) With non-unique secondary key: Locks on the non-unique secondary key (covering index) +set autocommit=0; +SELECT x from t3 where x = 1 FOR UPDATE; +x +1 +set autocommit=0; +lock will skip +SELECT x from t3 where x >= 0 and x <= 3 FOR UPDATE SKIP LOCKED; +x +0 +2 +3 +set autocommit=0; +lock will skip +SELECT x from t3 where x >= 0 FOR UPDATE SKIP LOCKED; +x +5 +lock will skip +SELECT x from t3 order by x FOR UPDATE SKIP LOCKED; +x +5 +lock will not wait, expecting error +SELECT x from t3 where x = 1 FOR UPDATE NOWAIT; +ERROR HY000: Failed to lock a record and didn't wait +commit; +commit; +commit; +### (4) Locks on both the primary key and the unique secondary key +set autocommit=0; +SELECT * from t2 where id = 1 FOR UPDATE; +id x +1 1 +set autocommit=0; +lock will skip +SELECT * from t2 where id >= 0 and id <= 3 FOR UPDATE SKIP LOCKED; +id x +0 0 +2 2 +3 3 +set autocommit=0; +lock will skip +SELECT * from t2 where id >= 0 FOR UPDATE SKIP LOCKED; +id x +5 5 +lock will skip +SELECT * from t2 order by id FOR UPDATE SKIP LOCKED; +id x +5 5 +lock will not wait, expecting error +SELECT * from t2 where id = 1 FOR UPDATE NOWAIT; +ERROR HY000: Failed to lock a record and didn't wait +commit; +commit; +commit; +### (5) Transaction 1 locks primary key only, transaction 2 locks both +set autocommit=0; +SELECT id from t2 where id = 1 FOR UPDATE; +id +1 +set autocommit=0; +lock will skip +SELECT * from t2 where x >= 0 and x <= 3 FOR UPDATE SKIP LOCKED; +id x +0 0 +2 2 +3 3 +set autocommit=0; +lock will skip +SELECT x from t2 where x >= 0 FOR UPDATE SKIP LOCKED; +x +5 +drop table t0, t1, t2, t3; diff --git a/mysql-test/suite/innodb/t/innodb_lock_skip_locked_nowait.test b/mysql-test/suite/innodb/t/innodb_lock_skip_locked_nowait.test new file mode 100644 index 0000000..a3ea49f --- /dev/null +++ b/mysql-test/suite/innodb/t/innodb_lock_skip_locked_nowait.test @@ -0,0 +1,236 @@ +--disable_warnings +drop table if exists t0, t1, t2, t3; +--enable_warnings + +# (0) Without primary key +create table t0 (id integer, x integer) engine=INNODB; +insert into t0 values(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); + +# (1) With primary key +create table t1 (id integer primary key, x integer) engine=INNODB; +insert into t1 values(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); + +# (2) With unique secondary key +create table t2 (id integer primary key, x integer, unique key x(x)) engine=INNODB; +insert into t2 values(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); + +# (3) With non-unique secondary key +create table t3 (id integer primary key, x integer, key x(x)) engine=INNODB; +insert into t3 values(0, 0), (1, 1), (2, 2), (3, 3), (4, 4), (5, 5); + +connect (con1,localhost,root,,); +connect (con2,localhost,root,,); +connect (con3,localhost,root,,); + +--echo ### (0.1) Without pirmary key: Locks on all rows + +connection con1; +set autocommit=0; +SELECT * from t0 where id = 1 FOR UPDATE; + +connection con2; +set autocommit=0; + +--echo lock will skip +SELECT * from t0 where id >= 0 FOR UPDATE SKIP LOCKED; + +--echo lock will skip +SELECT * from t0 order by id FOR UPDATE SKIP LOCKED; + +--echo lock will not wait, expecting error +--error ER_DB_FAILED_TO_LOCK_REC_NOWAIT +SELECT * from t0 where id = 1 FOR UPDATE NOWAIT; + +connection con1; +commit; + +connection con2; +commit; + +--echo ### (0.2) Without pirmary key: Locks on some rows with 'limit x' + +connection con1; +set autocommit=0; +SELECT * from t0 where id = 1 limit 1 FOR UPDATE; + +connection con2; +set autocommit=0; + +--echo lock will skip +SELECT * from t0 where id >= 0 FOR UPDATE SKIP LOCKED; + +--echo lock will skip +SELECT * from t0 order by id FOR UPDATE SKIP LOCKED; + +--echo lock will not wait, expecting error +--error ER_DB_FAILED_TO_LOCK_REC_NOWAIT +SELECT * from t0 where id = 1 FOR UPDATE NOWAIT; + +connection con1; +commit; + +connection con2; +commit; + +--echo ### (1) With pirmary key: Locks only on the record (covering index) + +connection con1; +set autocommit=0; +SELECT * from t1 where id = 1 FOR UPDATE; + +connection con2; +set autocommit=0; + +--echo lock will skip +SELECT * from t1 where id >= 0 and id <= 3 FOR UPDATE SKIP LOCKED; + +connection con3; +set autocommit=0; + +--echo lock will skip +SELECT * from t1 where id >= 0 FOR UPDATE SKIP LOCKED; + +--echo lock will skip +SELECT * from t1 order by id FOR UPDATE SKIP LOCKED; + +--echo lock will not wait, expecting error +--error ER_DB_FAILED_TO_LOCK_REC_NOWAIT +SELECT * from t1 where id = 1 FOR UPDATE NOWAIT; + +connection con1; +commit; + +connection con2; +commit; + +connection con3; +commit; + +--echo ### (2) With unique secondary key: Locks on the unique secondary key (covering index) + +connection con1; +set autocommit=0; +SELECT x from t2 where x = 1 FOR UPDATE; + +connection con2; +set autocommit=0; + +--echo lock will skip +SELECT x from t2 where x >= 0 and x <= 3 FOR UPDATE SKIP LOCKED; + +connection con3; +set autocommit=0; + +--echo lock will skip +SELECT x from t2 where x >= 0 FOR UPDATE SKIP LOCKED; + +--echo lock will skip +SELECT x from t2 order by x FOR UPDATE SKIP LOCKED; + +--echo lock will not wait, expecting error +--error ER_DB_FAILED_TO_LOCK_REC_NOWAIT +SELECT x from t2 where x = 1 FOR UPDATE NOWAIT; + +connection con1; +commit; + +connection con2; +commit; + +connection con3; +commit; + +--echo ### (3) With non-unique secondary key: Locks on the non-unique secondary key (covering index) + +connection con1; +set autocommit=0; +SELECT x from t3 where x = 1 FOR UPDATE; + +connection con2; +set autocommit=0; + +--echo lock will skip +SELECT x from t3 where x >= 0 and x <= 3 FOR UPDATE SKIP LOCKED; + +connection con3; +set autocommit=0; + +--echo lock will skip +SELECT x from t3 where x >= 0 FOR UPDATE SKIP LOCKED; + +--echo lock will skip +SELECT x from t3 order by x FOR UPDATE SKIP LOCKED; + +--echo lock will not wait, expecting error +--error ER_DB_FAILED_TO_LOCK_REC_NOWAIT +SELECT x from t3 where x = 1 FOR UPDATE NOWAIT; + +connection con1; +commit; + +connection con2; +commit; + +connection con3; +commit; + +--echo ### (4) Locks on both the primary key and the unique secondary key + +connection con1; +set autocommit=0; +SELECT * from t2 where id = 1 FOR UPDATE; + +connection con2; +set autocommit=0; + +--echo lock will skip +SELECT * from t2 where id >= 0 and id <= 3 FOR UPDATE SKIP LOCKED; + +connection con3; +set autocommit=0; + +--echo lock will skip +SELECT * from t2 where id >= 0 FOR UPDATE SKIP LOCKED; + +--echo lock will skip +SELECT * from t2 order by id FOR UPDATE SKIP LOCKED; + +--echo lock will not wait, expecting error +--error ER_DB_FAILED_TO_LOCK_REC_NOWAIT +SELECT * from t2 where id = 1 FOR UPDATE NOWAIT; + +connection con1; +commit; + +connection con2; +commit; + +connection con3; +commit; + +--echo ### (5) Transaction 1 locks primary key only, transaction 2 locks both + +connection con1; +set autocommit=0; +SELECT id from t2 where id = 1 FOR UPDATE; + +connection con2; +set autocommit=0; + +--echo lock will skip +SELECT * from t2 where x >= 0 and x <= 3 FOR UPDATE SKIP LOCKED; + +connection con3; +set autocommit=0; + +--echo lock will skip +SELECT x from t2 where x >= 0 FOR UPDATE SKIP LOCKED; + +disconnect con1; +disconnect con2; +disconnect con3; + +connection default; + +drop table t0, t1, t2, t3; +exit; diff --git a/sql/handler.h b/sql/handler.h index 4e1e3f0..75cfcf7 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -4158,6 +4158,27 @@ virtual THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to, enum thr_lock_type lock_type)=0; + /* + Overload this virtual function for the storage engines that + support x-lock types. + */ + virtual THR_LOCK_DATA **store_lock_with_x_type(THD *thd, + THR_LOCK_DATA **to, + enum thr_lock_type lock_type, + enum thr_x_lock_type x_lock_type) + { + DBUG_ASSERT(0); + return NULL; + } + + /* + Overload this virtual function and make it return true for the storage + engines that support x-lock types. + */ + virtual bool support_x_lock_type() + { + return false; + } /** Type of table for caching query */ virtual uint8 table_cache_type() { return HA_CACHE_TBL_NONTRANSACT; } diff --git a/sql/lex.h b/sql/lex.h index 542356c..a7c8c2c 100644 --- a/sql/lex.h +++ b/sql/lex.h @@ -351,6 +351,7 @@ { "LOCALTIME", SYM(NOW_SYM)}, { "LOCALTIMESTAMP", SYM(NOW_SYM)}, { "LOCK", SYM(LOCK_SYM)}, + { "LOCKED", SYM(LOCKED_SYM)}, { "LOCKS", SYM(LOCKS_SYM)}, { "LOGFILE", SYM(LOGFILE_SYM)}, { "LOGS", SYM(LOGS_SYM)}, @@ -583,6 +584,7 @@ { "SIGNAL", SYM(SIGNAL_SYM)}, { "SIGNED", SYM(SIGNED_SYM)}, { "SIMPLE", SYM(SIMPLE_SYM)}, + { "SKIP", SYM(SKIP_SYM)}, { "SLAVE", SYM(SLAVE)}, { "SLAVES", SYM(SLAVES)}, { "SLAVE_POS", SYM(SLAVE_POS_SYM)}, diff --git a/sql/lock.cc b/sql/lock.cc index ff21579..6a0ccac 100644 --- a/sql/lock.cc +++ b/sql/lock.cc @@ -803,9 +803,21 @@ lock_type= table->reginfo.lock_type; DBUG_ASSERT(lock_type != TL_WRITE_DEFAULT && lock_type != TL_READ_DEFAULT); locks_start= locks; - locks= table->file->store_lock(thd, locks, - (flags & GET_LOCK_ACTION_MASK) == GET_LOCK_UNLOCK ? TL_IGNORE : - lock_type); + + if (table->reginfo.x_lock_type != TL_X_LOCK_REGULAR && + table->file->support_x_lock_type()) + { + DBUG_ASSERT(table->reginfo.x_lock_type == TL_X_LOCK_SKIP_LOCKED); + locks= table->file->store_lock_with_x_type(thd, locks, + (flags & GET_LOCK_ACTION_MASK) == GET_LOCK_UNLOCK ? TL_IGNORE : + lock_type, table->reginfo.x_lock_type); + } else { + DBUG_ASSERT(table->reginfo.x_lock_type == TL_X_LOCK_REGULAR); + locks= table->file->store_lock(thd, locks, + (flags & GET_LOCK_ACTION_MASK) == GET_LOCK_UNLOCK ? TL_IGNORE : + lock_type); + } + if ((flags & GET_LOCK_ACTION_MASK) == GET_LOCK_STORE_LOCKS) { table->lock_position= (uint) (to - table_buf); diff --git a/sql/mdl.h b/sql/mdl.h index 9dbf9aa..ebe6441 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -18,6 +18,7 @@ #include "sql_plist.h" #include "ilist.h" +#include "thr_lock.h" #include #include #include diff --git a/sql/sql_base.cc b/sql/sql_base.cc index d02d130..3d60c29 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -4429,6 +4429,12 @@ some_routine_modifies_data); else tbl->reginfo.lock_type= tables->lock_type; + + /* Copy the X-lock type */ + tbl->reginfo.x_lock_type = tables->x_lock_type; + if (tables->lock_type != TL_WRITE && + tables->lock_type != TL_WRITE_DEFAULT) + DBUG_ASSERT(tbl->reginfo.x_lock_type == TL_X_LOCK_REGULAR); } } diff --git a/sql/sql_lex.cc b/sql/sql_lex.cc index dc5449d..57ab126 100644 --- a/sql/sql_lex.cc +++ b/sql/sql_lex.cc @@ -9651,7 +9651,10 @@ if (update_lock) { sel->lock_type= TL_WRITE; - sel->set_lock_for_tables(TL_WRITE, false); + if (skip_locked) + sel->set_lock_for_tables(TL_WRITE, false, TL_X_LOCK_SKIP_LOCKED); + else + sel->set_lock_for_tables(TL_WRITE, false); } else { diff --git a/sql/sql_lex.h b/sql/sql_lex.h index 10b7178..f4155b6 100644 --- a/sql/sql_lex.h +++ b/sql/sql_lex.h @@ -1408,7 +1408,8 @@ TABLE_LIST *convert_right_join(); List* get_item_list(); ulong get_table_join_options(); - void set_lock_for_tables(thr_lock_type lock_type, bool for_update); + void set_lock_for_tables(thr_lock_type lock_type, bool for_update, + thr_x_lock_type x_lock_type = TL_X_LOCK_REGULAR); /* This method created for reiniting LEX in mysql_admin_table() and can be used only if you are going remove all SELECT_LEX & units except belonger diff --git a/sql/sql_parse.cc b/sql/sql_parse.cc index f09d714..110c86c 100644 --- a/sql/sql_parse.cc +++ b/sql/sql_parse.cc @@ -8914,6 +8914,7 @@ Set lock for all tables in current select level. @param lock_type Lock to set for tables + @param x_lock_type X-lock type to set for tables @note If lock is a write lock, then tables->updating is set 1 @@ -8921,16 +8922,18 @@ query */ -void st_select_lex::set_lock_for_tables(thr_lock_type lock_type, bool for_update) +void st_select_lex::set_lock_for_tables(thr_lock_type lock_type, bool for_update, + thr_x_lock_type x_lock_type) { DBUG_ENTER("set_lock_for_tables"); - DBUG_PRINT("enter", ("lock_type: %d for_update: %d", lock_type, - for_update)); + DBUG_PRINT("enter", ("lock_type: %d x_lock_type: %d for_update: %d", + lock_type, x_lock_type, for_update)); for (TABLE_LIST *tables= table_list.first; tables; tables= tables->next_local) { tables->lock_type= lock_type; + tables->x_lock_type= x_lock_type; tables->updating= for_update; tables->mdl_request.set_type((lock_type >= TL_WRITE_ALLOW_WRITE) ? MDL_SHARED_WRITE : MDL_SHARED_READ); diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 0b6f099..d125021 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -901,6 +901,7 @@ %token LEVEL_SYM %token LIST_SYM %token LOCAL_SYM /* SQL-2003-R */ +%token LOCKED_SYM %token LOCKS_SYM %token LOGFILE_SYM %token LOGS_SYM @@ -1061,6 +1062,7 @@ %token SHUTDOWN %token SIGNED_SYM %token SIMPLE_SYM /* SQL-2003-N */ +%token SKIP_SYM %token SLAVE %token SLAVES %token SLAVE_POS_SYM @@ -9083,6 +9085,12 @@ $$.defined_lock= TRUE; $$.update_lock= TRUE; } + | FOR_SYM UPDATE_SYM SKIP_SYM LOCKED_SYM + { + $$.defined_lock= TRUE; + $$.update_lock= TRUE; + $$.skip_locked= TRUE; + } | LOCK_SYM IN_SYM SHARE_SYM MODE_SYM opt_lock_wait_timeout_new { $$= $5; @@ -15654,6 +15662,7 @@ | LESS_SYM | LEVEL_SYM | LIST_SYM + | LOCKED_SYM | LOCKS_SYM | LOGFILE_SYM | LOGS_SYM @@ -15782,6 +15791,7 @@ | SETVAL_SYM | SIMPLE_SYM | SHARE_SYM + | SKIP_SYM | SLAVE_POS_SYM | SLOW | SNAPSHOT_SYM diff --git a/sql/structs.h b/sql/structs.h index 76129f2..e98008a 100644 --- a/sql/structs.h +++ b/sql/structs.h @@ -173,6 +173,7 @@ typedef struct st_reginfo { /* Extra info about reg */ struct st_join_table *join_tab; /* Used by SELECT() */ enum thr_lock_type lock_type; /* How database is used */ + enum thr_x_lock_type x_lock_type; /* X-lock type */ bool not_exists_optimize; /* TRUE <=> range optimizer found that there is no rows satisfying @@ -803,6 +804,7 @@ uint defined_lock:1; uint update_lock:1; uint defined_timeout:1; + uint skip_locked:1; }; ulong timeout; diff --git a/sql/table.cc b/sql/table.cc index 6cd2b16..0f1c971 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -3962,6 +3962,7 @@ } outparam->reginfo.lock_type= TL_UNLOCK; + outparam->reginfo.x_lock_type= TL_X_LOCK_REGULAR; outparam->current_lock= F_UNLCK; records=0; if ((db_stat & HA_OPEN_KEYFILE) || (prgflag & DELAYED_OPEN)) @@ -5408,6 +5409,7 @@ fulltext_searched= 0; file->ft_handler= 0; reginfo.impossible_range= 0; + reginfo.x_lock_type= TL_X_LOCK_REGULAR; reginfo.join_tab= NULL; reginfo.not_exists_optimize= FALSE; created= TRUE; diff --git a/sql/table.h b/sql/table.h index 83e8b69..33e1bc7 100644 --- a/sql/table.h +++ b/sql/table.h @@ -2443,6 +2443,7 @@ /* call back function for asking handler about caching in query cache */ qc_engine_callback callback_func; thr_lock_type lock_type; + thr_x_lock_type x_lock_type; uint outer_join; /* Which join type */ uint shared; /* Used in multi-upd */ bool updatable; /* VIEW/TABLE can be updated now */ diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 3f3f164..189ec39 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -3074,6 +3074,7 @@ m_prebuilt->select_lock_type = LOCK_NONE; m_prebuilt->stored_select_lock_type = LOCK_NONE; + m_prebuilt->select_x_lock_type = LOCK_X_REGULAR; /* Always fetch all columns in the index record */ @@ -14843,6 +14844,7 @@ dtuple_set_n_fields(m_prebuilt->search_tuple, 0); m_prebuilt->select_lock_type = LOCK_NONE; + m_prebuilt->select_x_lock_type = LOCK_X_REGULAR; /* Scan this index. */ if (dict_index_is_spatial(index)) { @@ -15457,6 +15459,7 @@ init_table_handle_for_HANDLER(); m_prebuilt->select_lock_type = LOCK_X; m_prebuilt->stored_select_lock_type = LOCK_X; + m_prebuilt->select_x_lock_type = LOCK_X_REGULAR; error = row_lock_table(m_prebuilt); if (error != DB_SUCCESS) { @@ -15484,6 +15487,7 @@ no lock for consistent read (plain SELECT). */ m_prebuilt->select_lock_type = LOCK_NONE; + m_prebuilt->select_x_lock_type = LOCK_X_REGULAR; } else { /* Not a consistent read: restore the select_lock_type value. The value of @@ -15662,10 +15666,14 @@ if (lock_type == F_WRLCK) { /* If this is a SELECT, then it is in UPDATE TABLE ... - or SELECT ... FOR UPDATE */ + or SELECT ... FOR UPDATE (NOWAIT or SKIP LOCKED) */ + m_prebuilt->select_lock_type = LOCK_X; m_prebuilt->stored_select_lock_type = LOCK_X; } + else if (lock_type != F_UNLCK) { + DBUG_ASSERT(prebuilt->select_x_lock_type == LOCK_X_REGULAR); + } if (lock_type != F_UNLCK) { /* MySQL is setting a new table lock */ @@ -15689,6 +15697,7 @@ m_prebuilt->select_lock_type = LOCK_S; m_prebuilt->stored_select_lock_type = LOCK_S; + m_prebuilt->select_x_lock_type = LOCK_X_REGULAR; } /* Starting from 4.1.9, no InnoDB table lock is taken in LOCK @@ -16281,16 +16290,17 @@ @return pointer to the current element in the 'to' array. */ THR_LOCK_DATA** -ha_innobase::store_lock( +ha_innobase::store_lock_with_x_type( /*====================*/ THD* thd, /*!< in: user thread handle */ THR_LOCK_DATA** to, /*!< in: pointer to the current element in an array of pointers to lock structs; only used as return value */ - thr_lock_type lock_type) /*!< in: lock type to store in + thr_lock_type lock_type, /*!< in: lock type to store in 'lock'; this may also be TL_IGNORE */ + thr_x_lock_type x_lock_type) /*!< in: x_lock type to store*/ { /* Note that trx in this function is NOT necessarily m_prebuilt->trx because we call update_thd() later, in ::external_lock()! Failure to @@ -16359,9 +16369,11 @@ if (trx->isolation_level == TRX_ISO_SERIALIZABLE) { m_prebuilt->select_lock_type = LOCK_S; m_prebuilt->stored_select_lock_type = LOCK_S; + m_prebuilt->select_x_lock_type = LOCK_X_REGULAR; } else { m_prebuilt->select_lock_type = LOCK_NONE; m_prebuilt->stored_select_lock_type = LOCK_NONE; + m_prebuilt->select_x_lock_type = LOCK_X_REGULAR; } /* Check for DROP TABLE */ @@ -16422,18 +16434,27 @@ m_prebuilt->select_lock_type = LOCK_NONE; m_prebuilt->stored_select_lock_type = LOCK_NONE; + m_prebuilt->select_x_lock_type = LOCK_X_REGULAR; } else { m_prebuilt->select_lock_type = LOCK_S; m_prebuilt->stored_select_lock_type = LOCK_S; + m_prebuilt->select_x_lock_type = LOCK_X_REGULAR; } } else if (lock_type != TL_IGNORE) { /* We set possible LOCK_X value in external_lock, not yet - here even if this would be SELECT ... FOR UPDATE */ + here even if this would be SELECT ... FOR UPDATE (SKIP LOCKED) */ m_prebuilt->select_lock_type = LOCK_NONE; m_prebuilt->stored_select_lock_type = LOCK_NONE; + if (lock_type == TL_WRITE && + x_lock_type == TL_X_LOCK_SKIP_LOCKED) { + m_prebuilt->select_x_lock_type = LOCK_X_SKIP_LOCKED; + } else { + m_prebuilt->select_x_lock_type = LOCK_X_REGULAR; + } + } if (!trx_is_started(trx) diff --git a/storage/innobase/handler/ha_innodb.h b/storage/innobase/handler/ha_innodb.h index c5c5e8c..1163019 100644 --- a/storage/innobase/handler/ha_innodb.h +++ b/storage/innobase/handler/ha_innodb.h @@ -228,11 +228,21 @@ uint lock_count(void) const override; - THR_LOCK_DATA** store_lock( + THR_LOCK_DATA** store_lock_with_x_type( THD* thd, THR_LOCK_DATA** to, - thr_lock_type lock_type) override; + thr_lock_type lock_type, + thr_x_lock_type x_lock_type) override; + THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to, + thr_lock_type lock_type) + { + return store_lock_with_x_type(thd, to, lock_type, + TL_X_LOCK_REGULAR); + } + bool support_x_lock_type() { + return true; + } void init_table_handle_for_HANDLER() override; void get_auto_increment( diff --git a/storage/innobase/include/db0err.h b/storage/innobase/include/db0err.h index 6cfc63f..b209a07 100644 --- a/storage/innobase/include/db0err.h +++ b/storage/innobase/include/db0err.h @@ -47,6 +47,11 @@ DB_MISSING_HISTORY, /*!< required history data has been deleted due to lack of space in rollback segment */ + DB_FAILED_TO_LOCK_REC_SKIP_LOCKED,/*!< a record was locked by another + transaction so the current transaction + skipped without waitting and would + select the next one. this is for + SELECT ... FOR UPDATE SKIP LOCKED */ DB_CLUSTER_NOT_FOUND = 30, DB_TABLE_NOT_FOUND, DB_MUST_GET_MORE_FILE_SPACE, /*!< the database has to be stopped diff --git a/storage/innobase/include/lock0lock.h b/storage/innobase/include/lock0lock.h index 3b63b06..fcaec6d 100644 --- a/storage/innobase/include/lock0lock.h +++ b/storage/innobase/include/lock0lock.h @@ -335,6 +335,9 @@ records: LOCK_S or LOCK_X; the latter is possible in SELECT FOR UPDATE */ + x_lock_mode x_mode, /*!< in: mode of the x-lock: + LOCK_X_REGULAR or LOCK_X_SKIP_LOCKED, + this is for SELECT FOR UPDATE */ unsigned gap_mode,/*!< in: LOCK_ORDINARY, LOCK_GAP, or LOCK_REC_NOT_GAP */ que_thr_t* thr); /*!< in: query thread */ @@ -363,6 +366,9 @@ records: LOCK_S or LOCK_X; the latter is possible in SELECT FOR UPDATE */ + x_lock_mode x_mode, /*!< in: mode of the x-lock: + LOCK_X_REGULAR or LOCK_X_SKIP_LOCKED, + this is for SELECT FOR UPDATE */ unsigned gap_mode,/*!< in: LOCK_ORDINARY, LOCK_GAP, or LOCK_REC_NOT_GAP */ que_thr_t* thr); /*!< in: query thread */ diff --git a/storage/innobase/include/lock0types.h b/storage/innobase/include/lock0types.h index 2330737..0322173 100644 --- a/storage/innobase/include/lock0types.h +++ b/storage/innobase/include/lock0types.h @@ -49,6 +49,35 @@ LOCK_NONE_UNSET = 255 }; +/* X-lock modes : these are complements to LOCK_X + LOCK_X + LOCK_X_SKIP_LOCKED is for SELECT ... FOR UPDATE SKIP LOCKED. + LOCK_X_REGULAR is the default value, which means the LOCK_X is regular. + It will be ignored if X-lock modes combine with other lock modes. + + There are a couple of lock modes in InnoDB but basically there are two + categories: shared lock (LOCK_S) and exclusive lock (LOCK_X) (let's + ignore other lock modes for now). These lock modes define both the + locks *on* a row and the behavior when *adding* a lock on a row that + has been locked, i.e. when a lock is added on a row, this lock can be + LOCK_S or LOCK_X , and when a transaction is *adding* a LOCK_S / + LOCK_X on a row that has been locked by LOCK_S / LOCK_X (by other + transactions), it either can lock it or get blocked on it. But SKIP + LOCKED is *not* a new lock mode, i.e, a lock added on a row + is still either LOCK_S or LOCK_X, the new sub lock mode + LOCK_X_SKIP_LOCKED only defines the *behavior* that + when a transaction is trying to put a LOCK_X on a row has been locked + with a LOCK_X already (by another transaction), it won't be blocked, + instead it will skip it to find the next match if the sub mode is + LOCK_X_SKIP_LOCKED. So in a nutshell, this new sub mode + LOCK_X_SKIP_LOCKED introduce new non-blocking behavior when locking + conflict happens, it is *not* a new lock mode. +*/ +enum x_lock_mode { + LOCK_X_REGULAR = 0, /* regular */ + LOCK_X_SKIP_LOCKED, /* skip locked */ + LOCK_X_MODE_COUNT +}; + /** Convert the given enum value into string. @param[in] mode the lock mode @return human readable string of the given enum value */ diff --git a/storage/innobase/include/row0mysql.h b/storage/innobase/include/row0mysql.h index fc45482..0f5a8de 100644 --- a/storage/innobase/include/row0mysql.h +++ b/storage/innobase/include/row0mysql.h @@ -686,6 +686,7 @@ dtuple_t* clust_ref; /*!< prebuilt dtuple used in sel/upd/del */ lock_mode select_lock_type;/*!< LOCK_NONE, LOCK_S, or LOCK_X */ + x_lock_mode select_x_lock_type;/*!< LOCK_X_REGULAR or LOCK_X_SKIP_LOCKED */ lock_mode stored_select_lock_type;/*!< this field is used to remember the original select_lock_type that was decided in ha_innodb.cc, diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index 56c3aee..6cb6499 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -1842,6 +1842,9 @@ unsigned mode, /*!< in: lock mode: LOCK_X or LOCK_S possibly ORed to either LOCK_GAP or LOCK_REC_NOT_GAP */ + x_lock_mode x_mode, /*!< in: mode of the x-lock: + LOCK_X_REGULAR or LOCK_X_SKIP_LOCKED, + this is for SELECT FOR UPDATE */ const buf_block_t* block, /*!< in: buffer block containing the record */ ulint heap_no,/*!< in: heap number of record */ @@ -1889,8 +1892,11 @@ If another transaction has a non-gap conflicting request in the queue, as this transaction does not have a lock strong enough already granted on the - record, we have to wait. */ - err = lock_rec_enqueue_waiting( + record, we have to wait. */ + if (x_mode == LOCK_X_SKIP_LOCKED) + err = DB_FAILED_TO_LOCK_REC_SKIP_LOCKED; + else + err = lock_rec_enqueue_waiting( #ifdef WITH_WSREP c_lock, #endif /* WITH_WSREP */ @@ -5436,7 +5442,7 @@ return DB_SUCCESS; } - err = lock_rec_lock(TRUE, LOCK_X | LOCK_REC_NOT_GAP, + err = lock_rec_lock(TRUE, LOCK_X | LOCK_REC_NOT_GAP, LOCK_X_REGULAR, block, heap_no, index, thr); ut_ad(lock_rec_queue_validate(FALSE, block, rec, index, offsets)); @@ -5491,7 +5497,7 @@ index record, and this would not have been possible if another active transaction had modified this secondary index record. */ - err = lock_rec_lock(TRUE, LOCK_X | LOCK_REC_NOT_GAP, + err = lock_rec_lock(TRUE, LOCK_X | LOCK_REC_NOT_GAP, LOCK_X_REGULAR, block, heap_no, index, thr); #ifdef UNIV_DEBUG @@ -5548,6 +5554,9 @@ records: LOCK_S or LOCK_X; the latter is possible in SELECT FOR UPDATE */ + x_lock_mode x_mode, /*!< in: mode of the x-lock: + LOCK_X_REGULAR or LOCK_X_SKIP_LOCKED, + this is for SELECT FOR UPDATE */ unsigned gap_mode,/*!< in: LOCK_ORDINARY, LOCK_GAP, or LOCK_REC_NOT_GAP */ que_thr_t* thr) /*!< in: query thread */ @@ -5585,7 +5594,7 @@ return DB_SUCCESS; } - err = lock_rec_lock(FALSE, gap_mode | mode, + err = lock_rec_lock(FALSE, gap_mode | mode, x_mode, block, heap_no, index, thr); ut_ad(lock_rec_queue_validate(FALSE, block, rec, index, offsets)); @@ -5618,6 +5627,9 @@ records: LOCK_S or LOCK_X; the latter is possible in SELECT FOR UPDATE */ + x_lock_mode x_mode, /*!< in: mode of the x-lock: + LOCK_X_REGULAR or LOCK_X_SKIP_LOCKED, + this is for SELECT FOR UPDATE */ unsigned gap_mode,/*!< in: LOCK_ORDINARY, LOCK_GAP, or LOCK_REC_NOT_GAP */ que_thr_t* thr) /*!< in: query thread */ @@ -5650,7 +5662,7 @@ return DB_SUCCESS; } - err = lock_rec_lock(FALSE, gap_mode | mode, + err = lock_rec_lock(FALSE, gap_mode | mode, x_mode, block, heap_no, index, thr); ut_ad(lock_rec_queue_validate(FALSE, block, rec, index, offsets)); @@ -5699,7 +5711,8 @@ offsets = rec_get_offsets(rec, index, offsets, true, ULINT_UNDEFINED, &tmp_heap); err = lock_clust_rec_read_check_and_lock(flags, block, rec, index, - offsets, mode, gap_mode, thr); + offsets, mode, LOCK_X_REGULAR, + gap_mode, thr); if (tmp_heap) { mem_heap_free(tmp_heap); } diff --git a/storage/innobase/row/row0ins.cc b/storage/innobase/row/row0ins.cc index 5e41d03..0eb1442 100644 --- a/storage/innobase/row/row0ins.cc +++ b/storage/innobase/row/row0ins.cc @@ -1397,10 +1397,12 @@ if (dict_index_is_clust(index)) { err = lock_clust_rec_read_check_and_lock( - 0, block, rec, index, offsets, LOCK_S, type, thr); + 0, block, rec, index, offsets, LOCK_S, + LOCK_X_REGULAR, type, thr); } else { err = lock_sec_rec_read_check_and_lock( - 0, block, rec, index, offsets, LOCK_S, type, thr); + 0, block, rec, index, offsets, LOCK_S, + LOCK_X_REGULAR, type, thr); } return(err); @@ -1428,10 +1430,12 @@ if (dict_index_is_clust(index)) { err = lock_clust_rec_read_check_and_lock( - 0, block, rec, index, offsets, LOCK_X, type, thr); + 0, block, rec, index, offsets, LOCK_X, + LOCK_X_REGULAR, type, thr); } else { err = lock_sec_rec_read_check_and_lock( - 0, block, rec, index, offsets, LOCK_X, type, thr); + 0, block, rec, index, offsets, LOCK_X, + LOCK_X_REGULAR, type, thr); } return(err); diff --git a/storage/innobase/row/row0sel.cc b/storage/innobase/row/row0sel.cc index 7a859ab..60853c3 100644 --- a/storage/innobase/row/row0sel.cc +++ b/storage/innobase/row/row0sel.cc @@ -957,6 +957,7 @@ 0, btr_pcur_get_block(&plan->clust_pcur), clust_rec, index, offsets, node->row_lock_mode, + LOCK_X_REGULAR, trx->isolation_level <= TRX_ISO_READ_COMMITTED ? LOCK_REC_NOT_GAP : LOCK_ORDINARY, thr); @@ -1052,6 +1053,9 @@ dict_index_t* index, /*!< in: index */ const rec_offs* offsets,/*!< in: rec_get_offsets(rec, index) */ unsigned mode, /*!< in: lock mode */ + unsigned x_mode, /*!< in: mode of the x-lock: + LOCK_X_REGULAR or LOCK_X_SKIP_LOCKED, + this is for SELECT FOR UPDATE */ unsigned type, /*!< in: LOCK_ORDINARY, LOCK_GAP, or LOC_REC_NOT_GAP */ que_thr_t* thr, /*!< in: query thread */ @@ -1086,7 +1090,9 @@ err = lock_sec_rec_read_check_and_lock( 0, cur_block, rec, index, my_offsets, - static_cast(mode), type, thr); + static_cast(mode), + static_cast(x_mode), + type, thr); if (err == DB_LOCK_WAIT) { re_scan: @@ -1182,6 +1188,7 @@ err = lock_sec_rec_read_check_and_lock( 0, &match->block, rtr_rec->r_rec, index, my_offsets, static_cast(mode), + static_cast(x_mode), type, thr); if (err == DB_SUCCESS || err == DB_SUCCESS_LOCKED_REC) { @@ -1218,6 +1225,9 @@ dict_index_t* index, /*!< in: index */ const rec_offs* offsets,/*!< in: rec_get_offsets(rec, index) */ unsigned mode, /*!< in: lock mode */ + unsigned x_mode, /*!< in: mode of the x-lock: + LOCK_X_REGULAR or LOCK_X_SKIP_LOCKED, + this is for SELECT FOR UPDATE */ unsigned type, /*!< in: LOCK_ORDINARY, LOCK_GAP, or LOC_REC_NOT_GAP */ que_thr_t* thr, /*!< in: query thread */ @@ -1239,7 +1249,9 @@ if (dict_index_is_clust(index)) { err = lock_clust_rec_read_check_and_lock( 0, block, rec, index, offsets, - static_cast(mode), type, thr); + static_cast(mode), + static_cast(x_mode), + type, thr); } else { if (dict_index_is_spatial(index)) { @@ -1250,11 +1262,13 @@ return(DB_SUCCESS); } err = sel_set_rtr_rec_lock(pcur, rec, index, offsets, - mode, type, thr, mtr); + mode, x_mode, type, thr, mtr); } else { err = lock_sec_rec_read_check_and_lock( 0, block, rec, index, offsets, - static_cast(mode), type, thr); + static_cast(mode), + static_cast(x_mode), + type, thr); } } @@ -1718,6 +1732,7 @@ err = sel_set_rec_lock(&plan->pcur, next_rec, index, offsets, node->row_lock_mode, + LOCK_X_REGULAR, lock_type, thr, &mtr); switch (err) { @@ -1783,6 +1798,7 @@ err = sel_set_rec_lock(&plan->pcur, rec, index, offsets, node->row_lock_mode, lock_type, + LOCK_X_REGULAR, thr, &mtr); switch (err) { @@ -3394,6 +3410,7 @@ 0, btr_pcur_get_block(prebuilt->clust_pcur), clust_rec, clust_index, *offsets, prebuilt->select_lock_type, + prebuilt->select_x_lock_type, LOCK_REC_NOT_GAP, thr); @@ -4687,7 +4704,9 @@ err = sel_set_rec_lock(pcur, next_rec, index, offsets, prebuilt->select_lock_type, - LOCK_GAP, thr, &mtr); + prebuilt->select_x_lock_type, + LOCK_GAP, + thr, &mtr); switch (err) { case DB_SUCCESS_LOCKED_REC: @@ -4768,6 +4787,7 @@ err = sel_set_rec_lock(pcur, rec, index, offsets, prebuilt->select_lock_type, + prebuilt->select_x_lock_type, LOCK_ORDINARY, thr, &mtr); switch (err) { @@ -4902,8 +4922,9 @@ err = sel_set_rec_lock( pcur, rec, index, offsets, - prebuilt->select_lock_type, LOCK_GAP, - thr, &mtr); + prebuilt->select_lock_type, + prebuilt->select_x_lock_type, + LOCK_GAP, thr, &mtr); switch (err) { case DB_SUCCESS_LOCKED_REC: @@ -4937,8 +4958,9 @@ err = sel_set_rec_lock( pcur, rec, index, offsets, - prebuilt->select_lock_type, LOCK_GAP, - thr, &mtr); + prebuilt->select_lock_type, + prebuilt->select_x_lock_type, + LOCK_GAP, thr, &mtr); switch (err) { case DB_SUCCESS_LOCKED_REC: @@ -5059,6 +5081,7 @@ err = sel_set_rec_lock(pcur, rec, index, offsets, prebuilt->select_lock_type, + prebuilt->select_x_lock_type, lock_type, thr, &mtr); switch (err) { @@ -5073,6 +5096,8 @@ /* fall through */ case DB_SUCCESS: break; + case DB_FAILED_TO_LOCK_REC_SKIP_LOCKED: + goto next_rec; case DB_LOCK_WAIT: /* Lock wait for R-tree should already be handled in sel_set_rtr_rec_lock() */ @@ -5316,6 +5341,8 @@ goto next_rec; } break; + case DB_FAILED_TO_LOCK_REC_SKIP_LOCKED: + goto next_rec; case DB_SUCCESS_LOCKED_REC: ut_a(clust_rec != NULL); if (trx->isolation_level <= TRX_ISO_READ_COMMITTED) { diff --git a/storage/innobase/ut/ut0ut.cc b/storage/innobase/ut/ut0ut.cc index 3376698..7bb31bd 100644 --- a/storage/innobase/ut/ut0ut.cc +++ b/storage/innobase/ut/ut0ut.cc @@ -357,6 +357,8 @@ return("Duplicate key"); case DB_MISSING_HISTORY: return("Required history data has been deleted"); + case DB_FAILED_TO_LOCK_REC_SKIP_LOCKED: + return("Failed to lock record with skip-locked"); case DB_CLUSTER_NOT_FOUND: return("Cluster not found"); case DB_TABLE_NOT_FOUND: