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
* 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"
......
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