mirror of
https://github.com/MariaDB/server.git
synced 2025-02-01 11:31:51 +01:00
branches/zip: Refuse to use newly created indexes that may lack
history. This addresses Mantis issue #116. dict_index_t: Enable the storage of trx_id. row_prebuilt_t: Make many fields bit-fields to reduce the memory footprint. Add index_usable. ha_innobase::change_active_index(): Check if the index is usable and set prebuilt->index_usable accordingly. Unfortunately, the return status of this function is ignored by MySQL, and the actual refusal to use the index must be made in row_search_for_mysql(). row_search_for_mysql(): Return DB_MISSING_HISTORY if !prebuilt->index_usable. convert_error_code_to_mysql(): Map DB_MISSING_HISTORY to HA_ERR_TABLE_DEF_CHANGED. innodb-index.test: Add a test case where access to a newly created secondary index must be blocked for old transactions. rb://100 approved by Heikki Tuuri
This commit is contained in:
parent
dacfdf4a1d
commit
ab67f4eb4a
11 changed files with 110 additions and 71 deletions
|
@ -1,3 +1,12 @@
|
|||
2009-04-02 The InnoDB Team
|
||||
|
||||
* dict/dict0crea.c, handler/ha_innodb.cc, handler/ha_innodb.h,
|
||||
* include/dict0mem.h, include/row0merge.h, include/row0mysql.h,
|
||||
* mysql-test/innodb-index.result, mysql-test/innodb-index.test,
|
||||
* row/row0merge.c, row/row0sel.c:
|
||||
In consistent reads, refuse to use newly created indexes that may
|
||||
lack history.
|
||||
|
||||
2009-03-20 The InnoDB Team
|
||||
|
||||
* buf/buf0buf.c, include/log0recv.h, log/log0recv.c:
|
||||
|
|
|
@ -561,10 +561,8 @@ dict_build_index_def_step(
|
|||
|
||||
ins_node_set_new_row(node->ind_def, row);
|
||||
|
||||
#ifdef ROW_MERGE_IS_INDEX_USABLE
|
||||
/* Note that the index was created by this transaction. */
|
||||
index->trx_id = trx->id;
|
||||
#endif /* ROW_MERGE_IS_INDEX_USABLE */
|
||||
index->trx_id = (ib_uint64_t) ut_conv_dulint_to_longlong(trx->id);
|
||||
|
||||
return(DB_SUCCESS);
|
||||
}
|
||||
|
|
|
@ -720,6 +720,9 @@ convert_error_code_to_mysql(
|
|||
case DB_FOREIGN_DUPLICATE_KEY:
|
||||
return(HA_ERR_FOREIGN_DUPLICATE_KEY);
|
||||
|
||||
case DB_MISSING_HISTORY:
|
||||
return(HA_ERR_TABLE_DEF_CHANGED);
|
||||
|
||||
case DB_RECORD_NOT_FOUND:
|
||||
return(HA_ERR_NO_ACTIVE_RECORD);
|
||||
|
||||
|
@ -4689,38 +4692,6 @@ ha_innobase::try_semi_consistent_read(bool yes)
|
|||
}
|
||||
}
|
||||
|
||||
#ifdef ROW_MERGE_IS_INDEX_USABLE
|
||||
/**********************************************************************
|
||||
Check if an index can be used by the optimizer. */
|
||||
UNIV_INTERN
|
||||
bool
|
||||
ha_innobase::is_index_available(
|
||||
/*============================*/
|
||||
/* out: true if available else false*/
|
||||
uint keynr) /* in: index number to check */
|
||||
{
|
||||
DBUG_ENTER("ha_innobase::is_index_available");
|
||||
|
||||
if (table && keynr != MAX_KEY && table->s->keys > 0) {
|
||||
const dict_index_t* index;
|
||||
const KEY* key = table->key_info + keynr;
|
||||
|
||||
ut_ad(user_thd == ha_thd());
|
||||
ut_a(prebuilt->trx == thd_to_trx(user_thd));
|
||||
|
||||
index = dict_table_get_index_on_name(
|
||||
prebuilt->table, key->name);
|
||||
|
||||
if (!row_merge_is_index_usable(prebuilt->trx, index)) {
|
||||
|
||||
DBUG_RETURN(false);
|
||||
}
|
||||
}
|
||||
|
||||
DBUG_RETURN(true);
|
||||
}
|
||||
#endif /* ROW_MERGE_IS_INDEX_USABLE */
|
||||
|
||||
/**********************************************************************
|
||||
Initializes a handle to use an index. */
|
||||
UNIV_INTERN
|
||||
|
@ -5055,6 +5026,17 @@ ha_innobase::change_active_index(
|
|||
DBUG_RETURN(1);
|
||||
}
|
||||
|
||||
prebuilt->index_usable = row_merge_is_index_usable(prebuilt->trx,
|
||||
prebuilt->index);
|
||||
|
||||
if (UNIV_UNLIKELY(!prebuilt->index_usable)) {
|
||||
sql_print_warning("InnoDB: insufficient history for index %u",
|
||||
keynr);
|
||||
/* The caller seems to ignore this. Thus, we must check
|
||||
this again in row_search_for_mysql(). */
|
||||
DBUG_RETURN(2);
|
||||
}
|
||||
|
||||
ut_a(prebuilt->search_tuple != 0);
|
||||
|
||||
dtuple_set_n_fields(prebuilt->search_tuple, prebuilt->index->n_fields);
|
||||
|
|
|
@ -119,14 +119,6 @@ class ha_innobase: public handler
|
|||
void try_semi_consistent_read(bool yes);
|
||||
void unlock_row();
|
||||
|
||||
#ifdef ROW_MERGE_IS_INDEX_USABLE
|
||||
/** Check if an index can be used by this transaction.
|
||||
* @param keynr key number to check
|
||||
* @return true if available, false if the index
|
||||
* does not contain old records that exist
|
||||
* in the read view of this transaction */
|
||||
bool is_index_available(uint keynr);
|
||||
#endif /* ROW_MERGE_IS_INDEX_USABLE */
|
||||
int index_init(uint index, bool sorted);
|
||||
int index_end();
|
||||
int index_read(uchar * buf, const uchar * key,
|
||||
|
|
|
@ -279,11 +279,9 @@ struct dict_index_struct{
|
|||
index tree */
|
||||
rw_lock_t lock; /* read-write lock protecting the upper levels
|
||||
of the index tree */
|
||||
#ifdef ROW_MERGE_IS_INDEX_USABLE
|
||||
dulint trx_id; /* id of the transaction that created this
|
||||
index, or ut_dulint_zero if the index existed
|
||||
ib_uint64_t trx_id; /* id of the transaction that created this
|
||||
index, or 0 if the index existed
|
||||
when InnoDB was started up */
|
||||
#endif /* ROW_MERGE_IS_INDEX_USABLE */
|
||||
#endif /* !UNIV_HOTBACKUP */
|
||||
#ifdef UNIV_DEBUG
|
||||
ulint magic_n;/* magic number */
|
||||
|
|
|
@ -152,7 +152,6 @@ row_merge_create_index(
|
|||
dict_table_t* table, /* in: the index is on this table */
|
||||
const merge_index_def_t* /* in: the index definition */
|
||||
index_def);
|
||||
#ifdef ROW_MERGE_IS_INDEX_USABLE
|
||||
/*************************************************************************
|
||||
Check if a transaction can use an index. */
|
||||
UNIV_INTERN
|
||||
|
@ -163,7 +162,6 @@ row_merge_is_index_usable(
|
|||
the transaction else FALSE*/
|
||||
const trx_t* trx, /* in: transaction */
|
||||
const dict_index_t* index); /* in: index to check */
|
||||
#endif /* ROW_MERGE_IS_INDEX_USABLE */
|
||||
/*************************************************************************
|
||||
If there are views that refer to the old table name then we "attach" to
|
||||
the new instance of the table else we drop it immediately. */
|
||||
|
|
|
@ -571,52 +571,54 @@ struct row_prebuilt_struct {
|
|||
or ROW_PREBUILT_FREED when the
|
||||
struct has been freed */
|
||||
dict_table_t* table; /* Innobase table handle */
|
||||
dict_index_t* index; /* current index for a search, if
|
||||
any */
|
||||
trx_t* trx; /* current transaction handle */
|
||||
ibool sql_stat_start; /* TRUE when we start processing of
|
||||
unsigned sql_stat_start:1;/* TRUE when we start processing of
|
||||
an SQL statement: we may have to set
|
||||
an intention lock on the table,
|
||||
create a consistent read view etc. */
|
||||
ibool mysql_has_locked; /* this is set TRUE when MySQL
|
||||
unsigned mysql_has_locked:1; /* this is set TRUE when MySQL
|
||||
calls external_lock on this handle
|
||||
with a lock flag, and set FALSE when
|
||||
with the F_UNLOCK flag */
|
||||
ibool clust_index_was_generated;
|
||||
unsigned clust_index_was_generated:1;
|
||||
/* if the user did not define a
|
||||
primary key in MySQL, then Innobase
|
||||
automatically generated a clustered
|
||||
index where the ordering column is
|
||||
the row id: in this case this flag
|
||||
is set to TRUE */
|
||||
dict_index_t* index; /* current index for a search, if
|
||||
any */
|
||||
ulint read_just_key; /* set to 1 when MySQL calls
|
||||
unsigned index_usable:1; /* caches the value of
|
||||
row_merge_is_index_usable(trx,index) */
|
||||
unsigned read_just_key:1;/* set to 1 when MySQL calls
|
||||
ha_innobase::extra with the
|
||||
argument HA_EXTRA_KEYREAD; it is enough
|
||||
to read just columns defined in
|
||||
the index (i.e., no read of the
|
||||
clustered index record necessary) */
|
||||
ibool used_in_HANDLER;/* TRUE if we have been using this
|
||||
unsigned used_in_HANDLER:1;/* TRUE if we have been using this
|
||||
handle in a MySQL HANDLER low level
|
||||
index cursor command: then we must
|
||||
store the pcur position even in a
|
||||
unique search from a clustered index,
|
||||
because HANDLER allows NEXT and PREV
|
||||
in such a situation */
|
||||
ulint template_type; /* ROW_MYSQL_WHOLE_ROW,
|
||||
unsigned template_type:2;/* ROW_MYSQL_WHOLE_ROW,
|
||||
ROW_MYSQL_REC_FIELDS,
|
||||
ROW_MYSQL_DUMMY_TEMPLATE, or
|
||||
ROW_MYSQL_NO_TEMPLATE */
|
||||
ulint n_template; /* number of elements in the
|
||||
unsigned n_template:10; /* number of elements in the
|
||||
template */
|
||||
ulint null_bitmap_len;/* number of bytes in the SQL NULL
|
||||
unsigned null_bitmap_len:10;/* number of bytes in the SQL NULL
|
||||
bitmap at the start of a row in the
|
||||
MySQL format */
|
||||
ibool need_to_access_clustered; /* if we are fetching
|
||||
unsigned need_to_access_clustered:1; /* if we are fetching
|
||||
columns through a secondary index
|
||||
and at least one column is not in
|
||||
the secondary index, then this is
|
||||
set to TRUE */
|
||||
ibool templ_contains_blob;/* TRUE if the template contains
|
||||
unsigned templ_contains_blob:1;/* TRUE if the template contains
|
||||
BLOB column(s) */
|
||||
mysql_row_templ_t* mysql_template;/* template used to transform
|
||||
rows fast between MySQL and Innobase
|
||||
|
|
|
@ -1132,3 +1132,39 @@ t2 CREATE TABLE `t2` (
|
|||
) ENGINE=InnoDB DEFAULT CHARSET=latin1
|
||||
DROP TABLE t2;
|
||||
DROP TABLE t1;
|
||||
CREATE TABLE t1 (a INT, b CHAR(1)) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (3,'a'),(3,'b'),(1,'c'),(0,'d'),(1,'e');
|
||||
BEGIN;
|
||||
SELECT * FROM t1;
|
||||
a b
|
||||
3 a
|
||||
3 b
|
||||
1 c
|
||||
0 d
|
||||
1 e
|
||||
CREATE INDEX t1a ON t1(a);
|
||||
SELECT * FROM t1;
|
||||
a b
|
||||
3 a
|
||||
3 b
|
||||
1 c
|
||||
0 d
|
||||
1 e
|
||||
SELECT * FROM t1 FORCE INDEX(t1a) ORDER BY a;
|
||||
ERROR HY000: Table definition has changed, please retry transaction
|
||||
SELECT * FROM t1;
|
||||
a b
|
||||
3 a
|
||||
3 b
|
||||
1 c
|
||||
0 d
|
||||
1 e
|
||||
COMMIT;
|
||||
SELECT * FROM t1 FORCE INDEX(t1a) ORDER BY a;
|
||||
a b
|
||||
0 d
|
||||
1 c
|
||||
1 e
|
||||
3 a
|
||||
3 b
|
||||
DROP TABLE t1;
|
||||
|
|
|
@ -509,3 +509,26 @@ SHOW CREATE TABLE t2;
|
|||
|
||||
DROP TABLE t2;
|
||||
DROP TABLE t1;
|
||||
|
||||
connect (a,localhost,root,,);
|
||||
connect (b,localhost,root,,);
|
||||
connection a;
|
||||
CREATE TABLE t1 (a INT, b CHAR(1)) ENGINE=InnoDB;
|
||||
INSERT INTO t1 VALUES (3,'a'),(3,'b'),(1,'c'),(0,'d'),(1,'e');
|
||||
connection b;
|
||||
BEGIN;
|
||||
SELECT * FROM t1;
|
||||
connection a;
|
||||
CREATE INDEX t1a ON t1(a);
|
||||
connection b;
|
||||
SELECT * FROM t1;
|
||||
--error ER_TABLE_DEF_CHANGED
|
||||
SELECT * FROM t1 FORCE INDEX(t1a) ORDER BY a;
|
||||
SELECT * FROM t1;
|
||||
COMMIT;
|
||||
SELECT * FROM t1 FORCE INDEX(t1a) ORDER BY a;
|
||||
connection default;
|
||||
disconnect a;
|
||||
disconnect b;
|
||||
|
||||
DROP TABLE t1;
|
||||
|
|
|
@ -2208,12 +2208,11 @@ row_merge_create_index(
|
|||
|
||||
ut_a(index);
|
||||
|
||||
#ifdef ROW_MERGE_IS_INDEX_USABLE
|
||||
/* Note the id of the transaction that created this
|
||||
index, we use it to restrict readers from accessing
|
||||
this index, to ensure read consistency. */
|
||||
index->trx_id = trx->id;
|
||||
#endif /* ROW_MERGE_IS_INDEX_USABLE */
|
||||
index->trx_id = (ib_uint64_t)
|
||||
ut_conv_dulint_to_longlong(trx->id);
|
||||
} else {
|
||||
index = NULL;
|
||||
}
|
||||
|
@ -2221,7 +2220,6 @@ row_merge_create_index(
|
|||
return(index);
|
||||
}
|
||||
|
||||
#ifdef ROW_MERGE_IS_INDEX_USABLE
|
||||
/*************************************************************************
|
||||
Check if a transaction can use an index. */
|
||||
UNIV_INTERN
|
||||
|
@ -2231,13 +2229,11 @@ row_merge_is_index_usable(
|
|||
const trx_t* trx, /* in: transaction */
|
||||
const dict_index_t* index) /* in: index to check */
|
||||
{
|
||||
if (!trx->read_view) {
|
||||
return(TRUE);
|
||||
}
|
||||
|
||||
return(ut_dulint_cmp(index->trx_id, trx->read_view->low_limit_id) < 0);
|
||||
return(!trx->read_view || read_view_sees_trx_id(
|
||||
trx->read_view,
|
||||
ut_dulint_create((ulint) (index->trx_id >> 32),
|
||||
(ulint) index->trx_id & 0xFFFFFFFF)));
|
||||
}
|
||||
#endif /* ROW_MERGE_IS_INDEX_USABLE */
|
||||
|
||||
/*************************************************************************
|
||||
Drop the old table. */
|
||||
|
|
|
@ -3343,6 +3343,11 @@ row_search_for_mysql(
|
|||
return(DB_ERROR);
|
||||
}
|
||||
|
||||
if (UNIV_UNLIKELY(!prebuilt->index_usable)) {
|
||||
|
||||
return(DB_MISSING_HISTORY);
|
||||
}
|
||||
|
||||
if (UNIV_UNLIKELY(prebuilt->magic_n != ROW_PREBUILT_ALLOCATED)) {
|
||||
fprintf(stderr,
|
||||
"InnoDB: Error: trying to free a corrupt\n"
|
||||
|
|
Loading…
Add table
Reference in a new issue