Commit 3cd2c1e8 authored by Nikita Malyavin's avatar Nikita Malyavin

MDEV-29299 SELECT from table with vcol index reports warning

As of now innodb does not store trx_id for each record in secondary index.
The idea behind is following: let us store only per-page max_trx_id, and
delete-mark the records when they are deleted/updated.

If the read starts, it rememders the lowest id of currently active
transaction. Innodb refers to it as trx->read_view->m_up_limit_id.
See also ReadView::open.

When the page is fetched, its max_trx_id is compared to m_up_limit_id.
If the value is lower, and the secondary index record is not delete-marked,
then this page is just safe to read as is. Else, a clustered index could be
needed ato access. See page_get_max_trx_id call in row_search_mvcc, and the
corresponding switch (row_search_idx_cond_check(...)) below.

Virtual columns are required to be updated in case if the record was
delete-marked. The motivation behind it is documented in
Row_sel_get_clust_rec_for_mysql::operator() near
row_sel_sec_rec_is_for_clust_rec call.

This was basically a description why virtual column computation can
normally happen during SELECT, and, generally, a vcol index access.

Sometimes stats tables are updated by innodb. This starts a new
transaction, and it can happen that it didn't finish to the moment of
SELECT execution, forcing virtual columns recomputation. If the result was
a something that normally outputs a warning, like division by zero, then
it could be outputted in a racy manner.

The solution is to suppress the warnings when a column is computed
for the described purpose.
ignore_wrnings argument is added innobase_get_computed_value.
Currently, it is only true for a call from
row_sel_sec_rec_is_for_clust_rec.
parent 4fec99a2
SET default_storage_engine= innodb;
SET @saved_frequency = @@GLOBAL.innodb_purge_rseg_truncate_frequency;
SET GLOBAL innodb_purge_rseg_truncate_frequency = 1;
#
......@@ -310,3 +311,33 @@ ALTER TABLE t1 ADD KEY (b), ALGORITHM=INPLACE;
# Cleanup
DROP TABLE t1;
# End of 10.2 tests
#
# MDEV-29299 SELECT from table with vcol index reports warning
#
CREATE TABLE t(fld1 INT NOT NULL,
fld2 INT AS (100/fld1) VIRTUAL,
KEY(fld1), KEY(fld2));
CREATE TABLE t_odd(id int);
INSERT INTO t(fld1) VALUES(1), (2);
connect stop_purge,localhost,root;
START TRANSACTION WITH CONSISTENT SNAPSHOT;
INSERT INTO t_odd VALUES(10000);
connection default;
UPDATE IGNORE t SET fld1= 3 WHERE fld1= 2;
UPDATE IGNORE t SET fld1= 4 WHERE fld1= 3;
UPDATE IGNORE t SET fld1= 0 WHERE fld1= 4;
Warnings:
Warning 1365 Division by 0
SELECT fld2 FROM t FORCE INDEX(fld2);
fld2
NULL
100
SELECT fld2 FROM t FORCE INDEX(fld1);
fld2
100
NULL
Warnings:
Warning 1365 Division by 0
disconnect stop_purge;
DROP TABLE t, t_odd;
# End of 10.3 tests
--source include/have_innodb.inc
--source include/have_sequence.inc
SET default_storage_engine= innodb;
# Ensure that the history list length will actually be decremented by purge.
SET @saved_frequency = @@GLOBAL.innodb_purge_rseg_truncate_frequency;
SET GLOBAL innodb_purge_rseg_truncate_frequency = 1;
......@@ -334,3 +336,30 @@ DROP TABLE t1;
--echo # End of 10.2 tests
--echo #
--echo # MDEV-29299 SELECT from table with vcol index reports warning
--echo #
CREATE TABLE t(fld1 INT NOT NULL,
fld2 INT AS (100/fld1) VIRTUAL,
KEY(fld1), KEY(fld2));
CREATE TABLE t_odd(id int);
INSERT INTO t(fld1) VALUES(1), (2);
--connect stop_purge,localhost,root
# This prevents purge for records in t
START TRANSACTION WITH CONSISTENT SNAPSHOT;
INSERT INTO t_odd VALUES(10000);
--connection default
UPDATE IGNORE t SET fld1= 3 WHERE fld1= 2;
UPDATE IGNORE t SET fld1= 4 WHERE fld1= 3;
UPDATE IGNORE t SET fld1= 0 WHERE fld1= 4;
SELECT fld2 FROM t FORCE INDEX(fld2);
SELECT fld2 FROM t FORCE INDEX(fld1);
--disconnect stop_purge
DROP TABLE t, t_odd;
--echo # End of 10.3 tests
......@@ -1856,6 +1856,21 @@ class MDL_deadlock_and_lock_abort_error_handler: public Internal_error_handler
};
struct Suppress_warnings_error_handler : public Internal_error_handler
{
bool handle_condition(THD *thd,
uint sql_errno,
const char *sqlstate,
Sql_condition::enum_warning_level *level,
const char *msg,
Sql_condition **cond_hdl)
{
return *level == Sql_condition::WARN_LEVEL_WARN;
}
};
/**
Tables that were locked with LOCK TABLES statement.
......
......@@ -8155,12 +8155,22 @@ int TABLE::update_virtual_fields(handler *h, enum_vcol_update_mode update_mode)
DBUG_RETURN(in_use->is_error());
}
int TABLE::update_virtual_field(Field *vf)
/*
Calculate the virtual field value for a specified field.
@param vf A field to calculate
@param ignore_warnings Ignore calculation warnings. This usually
means that a calculation is internal and is
not expected to fail.
*/
int TABLE::update_virtual_field(Field *vf, bool ignore_warnings)
{
DBUG_ENTER("TABLE::update_virtual_field");
Query_arena backup_arena;
Counting_error_handler count_errors;
Suppress_warnings_error_handler warning_handler;
in_use->push_internal_handler(&count_errors);
if (ignore_warnings)
in_use->push_internal_handler(&warning_handler);
/*
TODO: this may impose memory leak until table flush.
See comment in
......@@ -8172,6 +8182,8 @@ int TABLE::update_virtual_field(Field *vf)
vf->vcol_info->expr->save_in_field(vf, 0);
in_use->restore_active_arena(expr_arena, &backup_arena);
in_use->pop_internal_handler();
if (ignore_warnings)
in_use->pop_internal_handler();
DBUG_RETURN(count_errors.errors);
}
......
......@@ -1610,7 +1610,7 @@ struct TABLE
uint actual_n_key_parts(KEY *keyinfo);
ulong actual_key_flags(KEY *keyinfo);
int update_virtual_field(Field *vf);
int update_virtual_field(Field *vf, bool ignore_warnings);
int update_virtual_fields(handler *h, enum_vcol_update_mode update_mode);
int update_default_fields(bool ignore_errors);
void evaluate_update_default_function();
......
......@@ -21138,7 +21138,8 @@ innobase_get_computed_value(
TABLE* mysql_table,
byte* mysql_rec,
const dict_table_t* old_table,
const upd_t* update)
const upd_t* update,
bool ignore_warnings)
{
byte rec_buf2[REC_VERSION_56_MAX_INDEX_COL_LEN];
byte* buf;
......@@ -21236,7 +21237,9 @@ innobase_get_computed_value(
MY_BITMAP *old_write_set = dbug_tmp_use_all_columns(mysql_table, &mysql_table->write_set);
MY_BITMAP *old_read_set = dbug_tmp_use_all_columns(mysql_table, &mysql_table->read_set);
ret = mysql_table->update_virtual_field(mysql_table->field[col->m_col.ind]);
ret = mysql_table->update_virtual_field(
mysql_table->field[col->m_col.ind],
ignore_warnings);
dbug_tmp_restore_column_map(&mysql_table->read_set, old_read_set);
dbug_tmp_restore_column_map(&mysql_table->write_set, old_write_set);
......
......@@ -916,7 +916,9 @@ void innobase_report_computed_value_failed(dtuple_t *row);
@param[in] old_table during ALTER TABLE, this is the old table
or NULL.
@param[in] update update vector for the parent row
@param[in] foreign foreign key information
@param[in] ignore_warnings ignore warnings during calculation. Usually
means that a calculation is internal and
should have no side effects.
@return the field filled with computed value */
dfield_t*
innobase_get_computed_value(
......@@ -929,8 +931,9 @@ innobase_get_computed_value(
THD* thd,
TABLE* mysql_table,
byte* mysql_rec,
const dict_table_t* old_table,
const upd_t* update);
const dict_table_t* old_table=NULL,
const upd_t* update=NULL,
bool ignore_warnings=false);
/** Get the computed value by supplying the base column values.
@param[in,out] table the table whose virtual column
......
......@@ -329,7 +329,8 @@ row_sel_sec_rec_is_for_clust_rec(
&heap, NULL, NULL,
thr_get_trx(thr)->mysql_thd,
thr->prebuilt->m_mysql_table,
record, NULL, NULL);
record, NULL, NULL,
true);
if (vfield == NULL) {
innobase_report_computed_value_failed(row);
......
......@@ -481,7 +481,7 @@
#ifdef MRN_MARIADB_P
# if (MYSQL_VERSION_ID >= 100203)
# define MRN_GENERATED_COLUMNS_UPDATE_VIRTUAL_FIELD(table, field) \
(table->update_virtual_field(field))
(table->update_virtual_field(field,false))
# else
# define MRN_GENERATED_COLUMNS_UPDATE_VIRTUAL_FIELD(table, field) \
(field->vcol_info->expr_item->save_in_field(field, 0))
......
......@@ -681,7 +681,7 @@ static int compute_vcols(MI_INFO *info, uchar *record, int keynum)
{
Field *f= table->field[kp->fieldnr - 1];
if (f->vcol_info)
table->update_virtual_field(f);
table->update_virtual_field(f, false);
}
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