Commit ab67f4eb authored by marko's avatar marko

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
parent dacfdf4a
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 2009-03-20 The InnoDB Team
* buf/buf0buf.c, include/log0recv.h, log/log0recv.c: * buf/buf0buf.c, include/log0recv.h, log/log0recv.c:
......
...@@ -561,10 +561,8 @@ dict_build_index_def_step( ...@@ -561,10 +561,8 @@ dict_build_index_def_step(
ins_node_set_new_row(node->ind_def, row); ins_node_set_new_row(node->ind_def, row);
#ifdef ROW_MERGE_IS_INDEX_USABLE
/* Note that the index was created by this transaction. */ /* Note that the index was created by this transaction. */
index->trx_id = trx->id; index->trx_id = (ib_uint64_t) ut_conv_dulint_to_longlong(trx->id);
#endif /* ROW_MERGE_IS_INDEX_USABLE */
return(DB_SUCCESS); return(DB_SUCCESS);
} }
......
...@@ -720,6 +720,9 @@ convert_error_code_to_mysql( ...@@ -720,6 +720,9 @@ convert_error_code_to_mysql(
case DB_FOREIGN_DUPLICATE_KEY: case DB_FOREIGN_DUPLICATE_KEY:
return(HA_ERR_FOREIGN_DUPLICATE_KEY); return(HA_ERR_FOREIGN_DUPLICATE_KEY);
case DB_MISSING_HISTORY:
return(HA_ERR_TABLE_DEF_CHANGED);
case DB_RECORD_NOT_FOUND: case DB_RECORD_NOT_FOUND:
return(HA_ERR_NO_ACTIVE_RECORD); return(HA_ERR_NO_ACTIVE_RECORD);
...@@ -4689,38 +4692,6 @@ ha_innobase::try_semi_consistent_read(bool yes) ...@@ -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. */ Initializes a handle to use an index. */
UNIV_INTERN UNIV_INTERN
...@@ -5055,6 +5026,17 @@ ha_innobase::change_active_index( ...@@ -5055,6 +5026,17 @@ ha_innobase::change_active_index(
DBUG_RETURN(1); 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); ut_a(prebuilt->search_tuple != 0);
dtuple_set_n_fields(prebuilt->search_tuple, prebuilt->index->n_fields); dtuple_set_n_fields(prebuilt->search_tuple, prebuilt->index->n_fields);
......
...@@ -119,14 +119,6 @@ class ha_innobase: public handler ...@@ -119,14 +119,6 @@ class ha_innobase: public handler
void try_semi_consistent_read(bool yes); void try_semi_consistent_read(bool yes);
void unlock_row(); 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_init(uint index, bool sorted);
int index_end(); int index_end();
int index_read(uchar * buf, const uchar * key, int index_read(uchar * buf, const uchar * key,
......
...@@ -279,11 +279,9 @@ struct dict_index_struct{ ...@@ -279,11 +279,9 @@ struct dict_index_struct{
index tree */ index tree */
rw_lock_t lock; /* read-write lock protecting the upper levels rw_lock_t lock; /* read-write lock protecting the upper levels
of the index tree */ of the index tree */
#ifdef ROW_MERGE_IS_INDEX_USABLE ib_uint64_t trx_id; /* id of the transaction that created this
dulint trx_id; /* id of the transaction that created this index, or 0 if the index existed
index, or ut_dulint_zero if the index existed
when InnoDB was started up */ when InnoDB was started up */
#endif /* ROW_MERGE_IS_INDEX_USABLE */
#endif /* !UNIV_HOTBACKUP */ #endif /* !UNIV_HOTBACKUP */
#ifdef UNIV_DEBUG #ifdef UNIV_DEBUG
ulint magic_n;/* magic number */ ulint magic_n;/* magic number */
......
...@@ -152,7 +152,6 @@ row_merge_create_index( ...@@ -152,7 +152,6 @@ row_merge_create_index(
dict_table_t* table, /* in: the index is on this table */ dict_table_t* table, /* in: the index is on this table */
const merge_index_def_t* /* in: the index definition */ const merge_index_def_t* /* in: the index definition */
index_def); index_def);
#ifdef ROW_MERGE_IS_INDEX_USABLE
/************************************************************************* /*************************************************************************
Check if a transaction can use an index. */ Check if a transaction can use an index. */
UNIV_INTERN UNIV_INTERN
...@@ -163,7 +162,6 @@ row_merge_is_index_usable( ...@@ -163,7 +162,6 @@ row_merge_is_index_usable(
the transaction else FALSE*/ the transaction else FALSE*/
const trx_t* trx, /* in: transaction */ const trx_t* trx, /* in: transaction */
const dict_index_t* index); /* in: index to check */ 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 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. */ the new instance of the table else we drop it immediately. */
......
...@@ -571,52 +571,54 @@ struct row_prebuilt_struct { ...@@ -571,52 +571,54 @@ struct row_prebuilt_struct {
or ROW_PREBUILT_FREED when the or ROW_PREBUILT_FREED when the
struct has been freed */ struct has been freed */
dict_table_t* table; /* Innobase table handle */ dict_table_t* table; /* Innobase table handle */
dict_index_t* index; /* current index for a search, if
any */
trx_t* trx; /* current transaction handle */ 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 SQL statement: we may have to set
an intention lock on the table, an intention lock on the table,
create a consistent read view etc. */ 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 calls external_lock on this handle
with a lock flag, and set FALSE when with a lock flag, and set FALSE when
with the F_UNLOCK flag */ with the F_UNLOCK flag */
ibool clust_index_was_generated; unsigned clust_index_was_generated:1;
/* if the user did not define a /* if the user did not define a
primary key in MySQL, then Innobase primary key in MySQL, then Innobase
automatically generated a clustered automatically generated a clustered
index where the ordering column is index where the ordering column is
the row id: in this case this flag the row id: in this case this flag
is set to TRUE */ is set to TRUE */
dict_index_t* index; /* current index for a search, if unsigned index_usable:1; /* caches the value of
any */ row_merge_is_index_usable(trx,index) */
ulint read_just_key; /* set to 1 when MySQL calls unsigned read_just_key:1;/* set to 1 when MySQL calls
ha_innobase::extra with the ha_innobase::extra with the
argument HA_EXTRA_KEYREAD; it is enough argument HA_EXTRA_KEYREAD; it is enough
to read just columns defined in to read just columns defined in
the index (i.e., no read of the the index (i.e., no read of the
clustered index record necessary) */ 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 handle in a MySQL HANDLER low level
index cursor command: then we must index cursor command: then we must
store the pcur position even in a store the pcur position even in a
unique search from a clustered index, unique search from a clustered index,
because HANDLER allows NEXT and PREV because HANDLER allows NEXT and PREV
in such a situation */ 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_REC_FIELDS,
ROW_MYSQL_DUMMY_TEMPLATE, or ROW_MYSQL_DUMMY_TEMPLATE, or
ROW_MYSQL_NO_TEMPLATE */ ROW_MYSQL_NO_TEMPLATE */
ulint n_template; /* number of elements in the unsigned n_template:10; /* number of elements in the
template */ 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 bitmap at the start of a row in the
MySQL format */ 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 columns through a secondary index
and at least one column is not in and at least one column is not in
the secondary index, then this is the secondary index, then this is
set to TRUE */ 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) */ BLOB column(s) */
mysql_row_templ_t* mysql_template;/* template used to transform mysql_row_templ_t* mysql_template;/* template used to transform
rows fast between MySQL and Innobase rows fast between MySQL and Innobase
......
...@@ -1132,3 +1132,39 @@ t2 CREATE TABLE `t2` ( ...@@ -1132,3 +1132,39 @@ t2 CREATE TABLE `t2` (
) ENGINE=InnoDB DEFAULT CHARSET=latin1 ) ENGINE=InnoDB DEFAULT CHARSET=latin1
DROP TABLE t2; DROP TABLE t2;
DROP TABLE t1; 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; ...@@ -509,3 +509,26 @@ SHOW CREATE TABLE t2;
DROP TABLE t2; DROP TABLE t2;
DROP TABLE t1; 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( ...@@ -2208,12 +2208,11 @@ row_merge_create_index(
ut_a(index); ut_a(index);
#ifdef ROW_MERGE_IS_INDEX_USABLE
/* Note the id of the transaction that created this /* Note the id of the transaction that created this
index, we use it to restrict readers from accessing index, we use it to restrict readers from accessing
this index, to ensure read consistency. */ this index, to ensure read consistency. */
index->trx_id = trx->id; index->trx_id = (ib_uint64_t)
#endif /* ROW_MERGE_IS_INDEX_USABLE */ ut_conv_dulint_to_longlong(trx->id);
} else { } else {
index = NULL; index = NULL;
} }
...@@ -2221,7 +2220,6 @@ row_merge_create_index( ...@@ -2221,7 +2220,6 @@ row_merge_create_index(
return(index); return(index);
} }
#ifdef ROW_MERGE_IS_INDEX_USABLE
/************************************************************************* /*************************************************************************
Check if a transaction can use an index. */ Check if a transaction can use an index. */
UNIV_INTERN UNIV_INTERN
...@@ -2231,13 +2229,11 @@ row_merge_is_index_usable( ...@@ -2231,13 +2229,11 @@ row_merge_is_index_usable(
const trx_t* trx, /* in: transaction */ const trx_t* trx, /* in: transaction */
const dict_index_t* index) /* in: index to check */ const dict_index_t* index) /* in: index to check */
{ {
if (!trx->read_view) { return(!trx->read_view || read_view_sees_trx_id(
return(TRUE); trx->read_view,
} ut_dulint_create((ulint) (index->trx_id >> 32),
(ulint) index->trx_id & 0xFFFFFFFF)));
return(ut_dulint_cmp(index->trx_id, trx->read_view->low_limit_id) < 0);
} }
#endif /* ROW_MERGE_IS_INDEX_USABLE */
/************************************************************************* /*************************************************************************
Drop the old table. */ Drop the old table. */
......
...@@ -3343,6 +3343,11 @@ row_search_for_mysql( ...@@ -3343,6 +3343,11 @@ row_search_for_mysql(
return(DB_ERROR); return(DB_ERROR);
} }
if (UNIV_UNLIKELY(!prebuilt->index_usable)) {
return(DB_MISSING_HISTORY);
}
if (UNIV_UNLIKELY(prebuilt->magic_n != ROW_PREBUILT_ALLOCATED)) { if (UNIV_UNLIKELY(prebuilt->magic_n != ROW_PREBUILT_ALLOCATED)) {
fprintf(stderr, fprintf(stderr,
"InnoDB: Error: trying to free a corrupt\n" "InnoDB: Error: trying to free a corrupt\n"
......
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