diff options
author | Marko Mäkelä <marko.makela@mariadb.com> | 2019-09-04 17:52:04 +0300 |
---|---|---|
committer | Marko Mäkelä <marko.makela@mariadb.com> | 2019-09-04 17:52:04 +0300 |
commit | 537f8594a60a1e09d6da0933b55764e0f8abbf5c (patch) | |
tree | 628c68bccb01538df826045ba6f3aaa45c29b93a | |
parent | 647d5b243020b799fd7112a801965f5780b86349 (diff) | |
parent | f605ce08b5c0e6ed6907d0639bcc5b5630e9b40a (diff) | |
download | mariadb-git-537f8594a60a1e09d6da0933b55764e0f8abbf5c.tar.gz |
Merge 10.2 into 10.3
33 files changed, 456 insertions, 374 deletions
diff --git a/libmariadb b/libmariadb -Subproject 7de639518ffe56a99ac805654381d17f42796be +Subproject 544b6f1d12f0e5b2a141129075ff2d64feb0e4c diff --git a/mysql-test/main/default.result b/mysql-test/main/default.result index c214e529d72..cf0788b2fb2 100644 --- a/mysql-test/main/default.result +++ b/mysql-test/main/default.result @@ -3357,18 +3357,26 @@ a b drop table t1; set sql_mode=default; create table t1 (a int default b, b int default 4, t text); -insert into t1 (b, t) values (5, '1 column is omitted'); -insert into t1 values (default, 5, '2 column gets DEFAULT, keyword'); -insert into t1 values (default(a), 5, '3 column gets DEFAULT(a), expression'); -insert into t1 values (default(a)+0, 5, '4 also expression DEFAULT(0)+0'); -insert into t1 values (b, 5, '5 the value of the DEFAULT(a), that is b'); +insert t1 (b, t) values (5, '1 column is omitted'); +insert t1 values (default, 5, '2 column gets DEFAULT, keyword'); +insert t1 values (default(a), 5, '3 column gets DEFAULT(a), expression'); +insert t1 values (default(a)+0, 5, '4 also expression DEFAULT(0)+0'); +insert t1 values (b, 5, '5 the value of the DEFAULT(a), that is b'); +insert t1 (t,b,a) values ('6 reversed, column gets DEFAULT, keyword', 5, default); +insert t1 (t,b,a) values ('7 reversed, column gets DEFAULT(a), expression', 5, default(a)); +insert t1 (t,b,a) values ('8 reversed, also expression DEFAULT(0)+0', 5, default(a)+0); +insert t1 (t,b,a) values ('9 reversed, the value of the DEFAULT(a), that is b', 5, b); select * from t1 order by t; a b t 5 5 1 column is omitted -5 5 2 column gets DEFAULT, keyword +4 5 2 column gets DEFAULT, keyword 4 5 3 column gets DEFAULT(a), expression 4 5 4 also expression DEFAULT(0)+0 4 5 5 the value of the DEFAULT(a), that is b +5 5 6 reversed, column gets DEFAULT, keyword +5 5 7 reversed, column gets DEFAULT(a), expression +5 5 8 reversed, also expression DEFAULT(0)+0 +5 5 9 reversed, the value of the DEFAULT(a), that is b drop table t1; create table t1 (col1 int default(-(default(col1)))); ERROR 01000: Expression for field `col1` is referring to uninitialized field `col1` diff --git a/mysql-test/main/default.test b/mysql-test/main/default.test index 44778bc568a..27e38eeeb49 100644 --- a/mysql-test/main/default.test +++ b/mysql-test/main/default.test @@ -2073,11 +2073,16 @@ set sql_mode=default; # MDEV-10201 Bad results for CREATE TABLE t1 (a INT DEFAULT b, b INT DEFAULT 4) # create table t1 (a int default b, b int default 4, t text); -insert into t1 (b, t) values (5, '1 column is omitted'); -insert into t1 values (default, 5, '2 column gets DEFAULT, keyword'); -insert into t1 values (default(a), 5, '3 column gets DEFAULT(a), expression'); -insert into t1 values (default(a)+0, 5, '4 also expression DEFAULT(0)+0'); -insert into t1 values (b, 5, '5 the value of the DEFAULT(a), that is b'); +insert t1 (b, t) values (5, '1 column is omitted'); +insert t1 values (default, 5, '2 column gets DEFAULT, keyword'); +insert t1 values (default(a), 5, '3 column gets DEFAULT(a), expression'); +insert t1 values (default(a)+0, 5, '4 also expression DEFAULT(0)+0'); +insert t1 values (b, 5, '5 the value of the DEFAULT(a), that is b'); +# and the same in a different order +insert t1 (t,b,a) values ('6 reversed, column gets DEFAULT, keyword', 5, default); +insert t1 (t,b,a) values ('7 reversed, column gets DEFAULT(a), expression', 5, default(a)); +insert t1 (t,b,a) values ('8 reversed, also expression DEFAULT(0)+0', 5, default(a)+0); +insert t1 (t,b,a) values ('9 reversed, the value of the DEFAULT(a), that is b', 5, b); select * from t1 order by t; drop table t1; diff --git a/mysql-test/main/function_defaults.result b/mysql-test/main/function_defaults.result index 62422752e17..4a8f64df352 100644 --- a/mysql-test/main/function_defaults.result +++ b/mysql-test/main/function_defaults.result @@ -3093,3 +3093,55 @@ a b 1999-12-01 11:22:33.000000 1999-12-01 11:22:33.000000 2001-09-09 04:46:40.000000 2001-09-09 04:46:40.000000 DROP TABLE t1; +create table t1 (t timestamp, i int, v timestamp as (t) virtual, key(v)); +insert t1 (t,i) values ('2006-03-01 23:59:59',1); +update t1 set i = 2; +check table t1; +Table Op Msg_type Msg_text +test.t1 check status OK +drop table t1; +create table t1 (t timestamp, i int); +create trigger tr1 before update on t1 for each row set @new:=new.t; +insert t1 (t,i) values ('2006-03-01 23:59:59', 1); +update t1 set i = 2; +select if(@new = t, 'correct', 'wrong') from t1; +if(@new = t, 'correct', 'wrong') +correct +drop table t1; +create table t1 (i int, j int as (i)); +create trigger tr1 before update on t1 for each row set @new:=new.j; +insert t1 (i) values (1); +update t1, t1 as t2 set t1.i = 2; +select if(@new = j, 'correct', 'wrong') from t1; +if(@new = j, 'correct', 'wrong') +correct +drop table t1; +create table t1 (a int, b varchar(20) default 'foo'); +insert t1 values (1,'bla'),(2, 'bar'); +select * from t1; +a b +1 bla +2 bar +update t1 set b=default where a=1; +select * from t1; +a b +1 foo +2 bar +drop table t1; +create table t1 ( +a int, +b timestamp default '2010-10-10 10:10:10' on update now(), +c varchar(100) default 'x'); +insert t1 (a) values (1),(2); +select * from t1; +a b c +1 2010-10-10 10:10:10 x +2 2010-10-10 10:10:10 x +set timestamp=unix_timestamp('2011-11-11 11-11-11'); +update t1 set b=default, c=default(b) where a=1; +select * from t1; +a b c +1 2010-10-10 10:10:10 2010-10-10 10:10:10 +2 2010-10-10 10:10:10 x +drop table t1; +set timestamp=default; diff --git a/mysql-test/main/function_defaults.test b/mysql-test/main/function_defaults.test index f8b23d0eda8..dd3ba109b2a 100644 --- a/mysql-test/main/function_defaults.test +++ b/mysql-test/main/function_defaults.test @@ -19,3 +19,51 @@ let $now=NOW(6); let $timestamp=TIMESTAMP(6); let $datetime=DATETIME(6); source 'include/function_defaults.inc'; + +# +# MDEV-20403 Assertion `0' or Assertion `btr_validate_index(index, 0)' failed in row_upd_sec_index_entry or error code 126: Index is corrupted upon UPDATE with TIMESTAMP..ON UPDATE +# + +# ON UPDATE NOW and indexed virtual columns +create table t1 (t timestamp, i int, v timestamp as (t) virtual, key(v)); +insert t1 (t,i) values ('2006-03-01 23:59:59',1); +update t1 set i = 2; +check table t1; +drop table t1; + +# ON UPDATE NOW and triggers +create table t1 (t timestamp, i int); +create trigger tr1 before update on t1 for each row set @new:=new.t; +insert t1 (t,i) values ('2006-03-01 23:59:59', 1); +update t1 set i = 2; +select if(@new = t, 'correct', 'wrong') from t1; +drop table t1; + +# triggers, virtual columns, multi-update +create table t1 (i int, j int as (i)); +create trigger tr1 before update on t1 for each row set @new:=new.j; +insert t1 (i) values (1); +update t1, t1 as t2 set t1.i = 2; +select if(@new = j, 'correct', 'wrong') from t1; +drop table t1; + +# SET xxx=DEFAULT +create table t1 (a int, b varchar(20) default 'foo'); +insert t1 values (1,'bla'),(2, 'bar'); +select * from t1; +update t1 set b=default where a=1; +select * from t1; +drop table t1; + +# ON UPDATE NOW and SET xxx=DEFAULT +create table t1 ( + a int, + b timestamp default '2010-10-10 10:10:10' on update now(), + c varchar(100) default 'x'); +insert t1 (a) values (1),(2); +select * from t1; +set timestamp=unix_timestamp('2011-11-11 11-11-11'); +update t1 set b=default, c=default(b) where a=1; +select * from t1; +drop table t1; +set timestamp=default; diff --git a/mysql-test/main/invisible_field.result b/mysql-test/main/invisible_field.result index 43a8b9d726b..9cdc54f2e43 100644 --- a/mysql-test/main/invisible_field.result +++ b/mysql-test/main/invisible_field.result @@ -538,7 +538,7 @@ a b insert into t2 values(1); select a,b from t2; a b -12 1 +NULL 1 drop table t1,t2; create table t1 (a int invisible, b int, c int); create table t2 (a int, b int, d int); diff --git a/mysql-test/suite/galera/disabled.def b/mysql-test/suite/galera/disabled.def index 115b4a39338..f123bc3126f 100644 --- a/mysql-test/suite/galera/disabled.def +++ b/mysql-test/suite/galera/disabled.def @@ -33,6 +33,7 @@ galera_var_node_address : MDEV-17151 Galera test failure galera_var_notify_cmd : MDEV-13549 Galera test failures galera_var_slave_threads : MDEV-19746 Galera test failures because of wsrep_slave_threads identification galera_sst_mariabackup_encrypt_with_key : MDEV-19926 Galera SST tests fail -galera_wan : MDEV-17259: Test failure on galera.galera_wan +galera_var_node_address : MDEV-20485 Galera test failure on galera.galera_var_node_address +galera_wan : MDEV-17259 Test failure on galera.galera_wan partition : MDEV-19958 Galera test failure on galera.partition query_cache: MDEV-15805 Test failure on galera.query_cache
\ No newline at end of file diff --git a/mysql-test/suite/innodb/r/recovery_shutdown.result b/mysql-test/suite/innodb/r/recovery_shutdown.result index d26163a0c4a..d2fd130add8 100644 --- a/mysql-test/suite/innodb/r/recovery_shutdown.result +++ b/mysql-test/suite/innodb/r/recovery_shutdown.result @@ -1,4 +1,5 @@ FLUSH TABLES; +call mtr.add_suppression("Found 1 prepared XA transactions"); # # MDEV-13797 InnoDB may hang if shutdown is initiated soon after startup # while rolling back recovered incomplete transactions @@ -8,10 +9,12 @@ BEGIN; COMMIT; connect con$c,localhost,root,,; CREATE TABLE t8 (a SERIAL, b INT UNIQUE, c INT UNIQUE) ENGINE=InnoDB; -BEGIN; +XA START 'x'; INSERT INTO t8 (a) SELECT NULL FROM t; UPDATE t8 SET a=a+100, b=a; DELETE FROM t8; +XA END 'x'; +XA PREPARE 'x'; connect con$c,localhost,root,,; CREATE TABLE t7 (a SERIAL, b INT UNIQUE, c INT UNIQUE) ENGINE=InnoDB; BEGIN; @@ -63,4 +66,7 @@ connection default; SET GLOBAL innodb_flush_log_at_trx_commit=1; CREATE TABLE u(a SERIAL) ENGINE=INNODB; FLUSH TABLES; +XA RECOVER; +formatID gtrid_length bqual_length data +1 1 0 x DROP TABLE t,u; diff --git a/mysql-test/suite/innodb/t/recovery_shutdown.test b/mysql-test/suite/innodb/t/recovery_shutdown.test index 3bd01653065..d72e785f21d 100644 --- a/mysql-test/suite/innodb/t/recovery_shutdown.test +++ b/mysql-test/suite/innodb/t/recovery_shutdown.test @@ -3,6 +3,7 @@ # Flush any open myisam tables from previous tests FLUSH TABLES; +call mtr.add_suppression("Found 1 prepared XA transactions"); --echo # --echo # MDEV-13797 InnoDB may hang if shutdown is initiated soon after startup @@ -23,6 +24,15 @@ dec $c; COMMIT; let $c = $trx; +connect (con$c,localhost,root,,); +eval CREATE TABLE t$c (a SERIAL, b INT UNIQUE, c INT UNIQUE) ENGINE=InnoDB; +XA START 'x'; +eval INSERT INTO t$c (a) SELECT NULL FROM t; +eval UPDATE t$c SET a=a+$size, b=a; +eval DELETE FROM t$c; +XA END 'x'; +XA PREPARE 'x'; +dec $c; while ($c) { connect (con$c,localhost,root,,); @@ -53,12 +63,17 @@ FLUSH TABLES; # Perform a slow shutdown in order to roll back all recovered transactions # and to avoid locking conflicts with the DROP TABLE below. +XA RECOVER; --disable_query_log SET GLOBAL innodb_fast_shutdown=0; --source include/restart_mysqld.inc --disable_query_log let $c = $trx; +disconnect con$c; +XA ROLLBACK 'x'; +eval DROP TABLE t$c; +dec $c; while ($c) { disconnect con$c; diff --git a/mysql-test/suite/rpl/disabled.def b/mysql-test/suite/rpl/disabled.def index f18e40f1f8c..4f3fb1cd0e3 100644 --- a/mysql-test/suite/rpl/disabled.def +++ b/mysql-test/suite/rpl/disabled.def @@ -17,5 +17,4 @@ rpl_row_binlog_max_cache_size : MDEV-11092 rpl_row_index_choice : MDEV-11666 rpl_parallel2 : fails after MDEV-16172 rpl_semi_sync_after_sync : fails after MDEV-16172 -mdev_17588: MDEV-20137 rpl_slave_grp_exec: MDEV-10514 diff --git a/mysql-test/suite/rpl/r/mdev_17588.result b/mysql-test/suite/rpl/r/mdev_17588.result index f6b4728bd88..54990a450e3 100644 --- a/mysql-test/suite/rpl/r/mdev_17588.result +++ b/mysql-test/suite/rpl/r/mdev_17588.result @@ -1,18 +1,16 @@ include/master-slave.inc [connection master] +connection slave; +include/stop_slave.inc +CHANGE MASTER TO master_use_gtid=slave_pos; +include/start_slave.inc connection master; create table t1 (a int) engine=innodb; create table t2 (a int); create table t3 (a int) engine=innodb; -include/save_master_gtid.inc connection slave; -include/wait_for_slave_sql_error.inc [errno=1286] -Last_Error = 'Error 'Unknown storage engine 'innodb'' on query. Default database: 'test'. Query: 'create table t1 (a int) engine=innodb'' -STOP SLAVE IO_THREAD; -include/wait_for_slave_to_stop.inc -SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1; -include/start_slave.inc -include/sync_with_master_gtid.inc +include/wait_for_slave_sql_error_and_skip.inc [errno=1286] +# Asserted this: Status should be 'Slave has read all relay log...' show tables; Tables_in_test t2 diff --git a/mysql-test/suite/rpl/t/mdev_17588.test b/mysql-test/suite/rpl/t/mdev_17588.test index e9a340cbd25..44644158dff 100644 --- a/mysql-test/suite/rpl/t/mdev_17588.test +++ b/mysql-test/suite/rpl/t/mdev_17588.test @@ -1,23 +1,28 @@ --source include/master-slave.inc --source include/have_innodb.inc +--connection slave +--source include/stop_slave.inc +CHANGE MASTER TO master_use_gtid=slave_pos; +--source include/start_slave.inc + --connection master create table t1 (a int) engine=innodb; create table t2 (a int); create table t3 (a int) engine=innodb; ---source include/save_master_gtid.inc +--save_master_pos --connection slave # Using ER_UNKNOWN_STORAGE_ENGINE wont work let $slave_sql_errno= 1286; ---source include/wait_for_slave_sql_error.inc ---let $status_items= Last_Error ---source include/show_slave_status.inc -STOP SLAVE IO_THREAD; -source include/wait_for_slave_to_stop.inc; -SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1; ---source include/start_slave.inc ---source include/sync_with_master_gtid.inc +--source include/wait_for_slave_sql_error_and_skip.inc +--sync_with_master + +--let $assert_text= Status should be 'Slave has read all relay log...' +--let $assert_cond= "[SHOW SLAVE STATUS, Slave_SQL_Running_State, 1]" +#Like "Slave has read all relay log%" +--source include/rpl_assert.inc + show tables; show create table t2; --error ER_NO_SUCH_TABLE diff --git a/sql/field.cc b/sql/field.cc index 75798999cc1..0d9797143fa 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -11422,33 +11422,6 @@ key_map Field::get_possible_keys() } -/** - Mark the field as having an explicit default value. - - @param value if available, the value that the field is being set to - - @note - Fields that have an explicit default value should not be updated - automatically via the DEFAULT or ON UPDATE functions. The functions - that deal with data change functionality (INSERT/UPDATE/LOAD), - determine if there is an explicit value for each field before performing - the data change, and call this method to mark the field. - - If the 'value' parameter is NULL, then the field is marked unconditionally - as having an explicit value. If 'value' is not NULL, then it can be further - analyzed to check if it really should count as a value. -*/ - -bool Field::set_explicit_default(Item *value) -{ - if (value->type() == Item::DEFAULT_VALUE_ITEM && - !((Item_default_value*)value)->arg) - return false; - set_has_explicit_value(); - return true; -} - - bool Field::validate_value_in_record_with_warn(THD *thd, const uchar *record) { my_bitmap_map *old_map= dbug_tmp_use_all_columns(table, table->read_set); diff --git a/sql/field.h b/sql/field.h index ce6e7e387ed..c2d1d241e27 100644 --- a/sql/field.h +++ b/sql/field.h @@ -1001,7 +1001,6 @@ public: { bitmap_clear_bit(&table->has_value_set, field_index); } - bool set_explicit_default(Item *value); virtual my_time_t get_timestamp(const uchar *pos, ulong *sec_part) const { DBUG_ASSERT(0); return 0; } @@ -1010,14 +1009,6 @@ public: return get_timestamp(ptr, sec_part); } - /** - Evaluates the @c UPDATE default function, if one exists, and stores the - result in the record buffer. If no such function exists for the column, - or the function is not valid for the column's data type, invoking this - function has no effect. - */ - virtual int evaluate_update_default_function() { return 0; } - virtual bool binary() const { return 1; } virtual bool zero_pack() const { return 1; } virtual enum ha_base_keytype key_type() const { return HA_KEYTYPE_BINARY; } @@ -2686,13 +2677,6 @@ public: void sql_type(String &str) const; bool zero_pack() const { return 0; } int set_time(); - int evaluate_update_default_function() - { - int res= 0; - if (has_update_default_function()) - res= set_time(); - return res; - } /* Get TIMESTAMP field value as seconds since begging of Unix Epoch */ my_time_t get_timestamp(const uchar *pos, ulong *sec_part) const; my_time_t get_timestamp(ulong *sec_part) const @@ -3148,13 +3132,6 @@ public: bool get_date(MYSQL_TIME *ltime, ulonglong fuzzydate) { return Field_datetime::get_TIME(ltime, ptr, fuzzydate); } int set_time(); - int evaluate_update_default_function() - { - int res= 0; - if (has_update_default_function()) - res= set_time(); - return res; - } uchar *pack(uchar* to, const uchar *from, uint max_length __attribute__((unused))) { diff --git a/sql/handler.h b/sql/handler.h index 154fc6da9d1..00698f853e3 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -802,9 +802,9 @@ struct xid_t { char data[XIDDATASIZE]; // not \0-terminated ! xid_t() {} /* Remove gcc warning */ - bool eq(struct xid_t *xid) + bool eq(struct xid_t *xid) const { return !xid->is_null() && eq(xid->gtrid_length, xid->bqual_length, xid->data); } - bool eq(long g, long b, const char *d) + bool eq(long g, long b, const char *d) const { return !is_null() && g == gtrid_length && b == bqual_length && !memcmp(d, data, g+b); } void set(struct xid_t *xid) { memcpy(this, xid, xid->length()); } diff --git a/sql/item.cc b/sql/item.cc index f6b4fd01636..ca93c71cd7b 100644 --- a/sql/item.cc +++ b/sql/item.cc @@ -9403,8 +9403,6 @@ int Item_default_value::save_in_field(Field *field_arg, bool no_conversions) return Item_field::save_in_field(field_arg, no_conversions); } - if (field_arg->default_value && field_arg->default_value->flags) - return 0; // defaut fields will be set later, no need to do it twice return field_arg->save_in_field_default_value(context->error_processor == &view_error_processor); } @@ -9651,7 +9649,7 @@ bool Item_trigger_field::set_value(THD *thd, sp_rcontext * /*ctx*/, Item **it) int err_code= item->save_in_field(field, 0); field->table->copy_blobs= copy_blobs_saved; - field->set_explicit_default(item); + field->set_has_explicit_value(); return err_code < 0; } diff --git a/sql/sql_base.cc b/sql/sql_base.cc index 3483c9381e8..458c921e01b 100644 --- a/sql/sql_base.cc +++ b/sql/sql_base.cc @@ -8343,7 +8343,7 @@ fill_record(THD *thd, TABLE *table_arg, List<Item> &fields, List<Item> &values, rfield->move_field_offset((my_ptrdiff_t) (table->record[1] - table->record[0])); } - rfield->set_explicit_default(value); + rfield->set_has_explicit_value(); } if (update && thd->variables.sql_mode & MODE_SIMULTANEOUS_ASSIGNMENT) @@ -8362,9 +8362,13 @@ fill_record(THD *thd, TABLE *table_arg, List<Item> &fields, List<Item> &values, } } - if (!update && table_arg->default_field && - table_arg->update_default_fields(0, ignore_errors)) - goto err; + if (update) + table_arg->evaluate_update_default_function(); + else + if (table_arg->default_field && + table_arg->update_default_fields(ignore_errors)) + goto err; + /* Update virtual fields */ if (table_arg->vfield && table_arg->update_virtual_fields(table_arg->file, VCOL_UPDATE_FOR_WRITE)) @@ -8554,7 +8558,6 @@ fill_record(THD *thd, TABLE *table, Field **ptr, List<Item> &values, { List_iterator_fast<Item> v(values); List<TABLE> tbl_list; - bool all_fields_have_values= true; Item *value; Field *field; bool abort_on_warning_saved= thd->abort_on_warning; @@ -8585,10 +8588,7 @@ fill_record(THD *thd, TABLE *table, Field **ptr, List<Item> &values, DBUG_ASSERT(field->table == table); if (unlikely(field->invisible)) - { - all_fields_have_values= false; continue; - } else value=v++; @@ -8617,11 +8617,8 @@ fill_record(THD *thd, TABLE *table, Field **ptr, List<Item> &values, else if (value->save_in_field(field, 0) < 0) goto err; - all_fields_have_values &= field->set_explicit_default(value); + field->set_has_explicit_value(); } - if (!all_fields_have_values && table->default_field && - table->update_default_fields(0, ignore_errors)) - goto err; /* Update virtual fields */ thd->abort_on_warning= FALSE; if (table->vfield && diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 9669c002731..2a7fca591c2 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1835,10 +1835,7 @@ int write_record(THD *thd, TABLE *table,COPY_INFO *info) be updated as if this is an UPDATE. */ if (different_records && table->default_field) - { - if (table->update_default_fields(1, info->ignore)) - goto err; - } + table->evaluate_update_default_function(); /* CHECK OPTION for VIEW ... ON DUPLICATE KEY UPDATE ... */ res= info->table_list->view_check_option(table->in_use, info->ignore); @@ -3856,7 +3853,7 @@ int select_insert::send_data(List<Item> &values) thd->count_cuted_fields= CHECK_FIELD_WARN; // Calculate cuted fields store_values(values); if (table->default_field && - unlikely(table->update_default_fields(0, info.ignore))) + unlikely(table->update_default_fields(info.ignore))) DBUG_RETURN(1); thd->count_cuted_fields= CHECK_FIELD_ERROR_FOR_NULL; if (unlikely(thd->is_error())) diff --git a/sql/sql_table.cc b/sql/sql_table.cc index 644bba9051f..2df5858c6ea 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -9819,7 +9819,7 @@ do_continue:; thd->count_cuted_fields= CHECK_FIELD_EXPRESSION; altered_table->reset_default_fields(); if (altered_table->default_field && - altered_table->update_default_fields(0, 1)) + altered_table->update_default_fields(true)) goto err_new_table_cleanup; thd->count_cuted_fields= CHECK_FIELD_IGNORE; @@ -10515,7 +10515,7 @@ copy_data_between_tables(THD *thd, TABLE *from, TABLE *to, prev_insert_id= to->file->next_insert_id; if (to->default_field) - to->update_default_fields(0, ignore); + to->update_default_fields(ignore); if (to->vfield) to->update_virtual_fields(to->file, VCOL_UPDATE_FOR_WRITE); diff --git a/sql/sql_update.cc b/sql/sql_update.cc index e846424ef13..18ab69d3557 100644 --- a/sql/sql_update.cc +++ b/sql/sql_update.cc @@ -69,17 +69,20 @@ bool compare_record(const TABLE *table) { DBUG_ASSERT(records_are_comparable(table)); - if ((table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ) != 0) + if (table->file->ha_table_flags() & HA_PARTIAL_COLUMN_READ || + table->s->has_update_default_function) { /* Storage engine may not have read all columns of the record. Fields (including NULL bits) not in the write_set may not have been read and can therefore not be compared. + Or ON UPDATE DEFAULT NOW() could've changed field values, including + NULL bits. */ for (Field **ptr= table->field ; *ptr != NULL; ptr++) { Field *field= *ptr; - if (bitmap_is_set(table->write_set, field->field_index)) + if (field->has_explicit_value() && !field->vcol_info) { if (field->real_maybe_null()) { @@ -111,8 +114,9 @@ bool compare_record(const TABLE *table) /* Compare updated fields */ for (Field **ptr= table->field ; *ptr ; ptr++) { - if (bitmap_is_set(table->write_set, (*ptr)->field_index) && - (*ptr)->cmp_binary_offset(table->s->rec_buff_length)) + Field *field= *ptr; + if (field->has_explicit_value() && !field->vcol_info && + field->cmp_binary_offset(table->s->rec_buff_length)) return TRUE; } return FALSE; @@ -897,11 +901,6 @@ update_begin: if (!can_compare_record || compare_record(table)) { - if (table->default_field && table->update_default_fields(1, ignore)) - { - error= 1; - break; - } if ((res= table_list->view_check_option(thd, ignore)) != VIEW_CHECK_OK) { @@ -2379,10 +2378,6 @@ int multi_update::send_data(List<Item> ¬_used_values) { int error; - if (table->default_field && - unlikely(table->update_default_fields(1, ignore))) - DBUG_RETURN(1); - if ((error= cur_table->view_check_option(thd, ignore)) != VIEW_CHECK_OK) { @@ -2699,6 +2694,10 @@ int multi_update::do_updates() copy_field_ptr->to_field->set_has_explicit_value(); } + table->evaluate_update_default_function(); + if (table->vfield && + table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_WRITE)) + goto err2; if (table->triggers && table->triggers->process_triggers(thd, TRG_EVENT_UPDATE, TRG_ACTION_BEFORE, TRUE)) @@ -2707,12 +2706,6 @@ int multi_update::do_updates() if (!can_compare_record || compare_record(table)) { int error; - if (table->default_field && - (error= table->update_default_fields(1, ignore))) - goto err2; - if (table->vfield && - table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_WRITE)) - goto err2; if ((error= cur_table->view_check_option(thd, ignore)) != VIEW_CHECK_OK) { diff --git a/sql/table.cc b/sql/table.cc index e1dd3f41033..44d804d4cc7 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -7930,7 +7930,7 @@ int TABLE::update_virtual_field(Field *vf) ignore_errors == 0. If set then an error was generated. */ -int TABLE::update_default_fields(bool update_command, bool ignore_errors) +int TABLE::update_default_fields(bool ignore_errors) { Query_arena backup_arena; Field **field_ptr; @@ -7950,14 +7950,9 @@ int TABLE::update_default_fields(bool update_command, bool ignore_errors) */ if (!field->has_explicit_value()) { - if (!update_command) - { - if (field->default_value && - (field->default_value->flags || field->flags & BLOB_FLAG)) - res|= (field->default_value->expr->save_in_field(field, 0) < 0); - } - else - res|= field->evaluate_update_default_function(); + if (field->default_value && + (field->default_value->flags || field->flags & BLOB_FLAG)) + res|= (field->default_value->expr->save_in_field(field, 0) < 0); if (!ignore_errors && res) { my_error(ER_CALCULATING_DEFAULT_VALUE, MYF(0), field->field_name.str); @@ -7971,6 +7966,21 @@ int TABLE::update_default_fields(bool update_command, bool ignore_errors) } +void TABLE::evaluate_update_default_function() +{ + DBUG_ENTER("TABLE::evaluate_update_default_function"); + + if (s->has_update_default_function) + for (Field **field_ptr= default_field; *field_ptr ; field_ptr++) + { + Field *field= (*field_ptr); + if (!field->has_explicit_value() && field->has_update_default_function()) + field->set_time(); + } + DBUG_VOID_RETURN; +} + + void TABLE::vers_update_fields() { bitmap_set_bit(write_set, vers_start_field()->field_index); diff --git a/sql/table.h b/sql/table.h index d5a735de5ed..494d6628ebe 100644 --- a/sql/table.h +++ b/sql/table.h @@ -1489,7 +1489,8 @@ public: ulong actual_key_flags(KEY *keyinfo); int update_virtual_field(Field *vf); int update_virtual_fields(handler *h, enum_vcol_update_mode update_mode); - int update_default_fields(bool update, bool ignore_errors); + int update_default_fields(bool ignore_errors); + void evaluate_update_default_function(); void reset_default_fields(); inline ha_rows stat_records() { return used_stat_records; } diff --git a/storage/innobase/include/lock0lock.h b/storage/innobase/include/lock0lock.h index 91ee6b07c40..83c356a8879 100644 --- a/storage/innobase/include/lock0lock.h +++ b/storage/innobase/include/lock0lock.h @@ -482,14 +482,10 @@ lock_rec_unlock( const buf_block_t* block, /*!< in: buffer block containing rec */ const rec_t* rec, /*!< in: record */ lock_mode lock_mode);/*!< in: LOCK_S or LOCK_X */ -/*********************************************************************//** -Releases a transaction's locks, and releases possible other transactions -waiting because of these locks. Change the state of the transaction to -TRX_STATE_COMMITTED_IN_MEMORY. */ -void -lock_trx_release_locks( -/*===================*/ - trx_t* trx); /*!< in/out: transaction */ + +/** Release the explicit locks of a committing transaction, +and release possible other transactions waiting because of these locks. */ +void lock_trx_release_locks(trx_t* trx); /*********************************************************************//** Calculates the fold value of a page file address: used in inserting or diff --git a/storage/innobase/include/row0vers.h b/storage/innobase/include/row0vers.h index 38fd952388d..948804beafc 100644 --- a/storage/innobase/include/row0vers.h +++ b/storage/innobase/include/row0vers.h @@ -44,7 +44,8 @@ index record. @param[in] rec secondary index record @param[in] index secondary index @param[in] offsets rec_get_offsets(rec, index) -@return the active transaction; trx->release_reference() must be invoked +@return the active transaction; state must be rechecked after +trx_mutex_enter(), and trx->release_reference() must be invoked @retval NULL if the record was committed */ trx_t* row_vers_impl_x_locked( diff --git a/storage/innobase/include/trx0sys.h b/storage/innobase/include/trx0sys.h index 8705809a373..c92907236e6 100644 --- a/storage/innobase/include/trx0sys.h +++ b/storage/innobase/include/trx0sys.h @@ -514,8 +514,10 @@ class rw_trx_hash_t { ut_ad(!trx->read_only || !trx->rsegs.m_redo.rseg); ut_ad(!trx_is_autocommit_non_locking(trx)); + /* trx->state can be anything except TRX_STATE_NOT_STARTED */ mutex_enter(&trx->mutex); ut_ad(trx_state_eq(trx, TRX_STATE_ACTIVE) || + trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY) || trx_state_eq(trx, TRX_STATE_PREPARED_RECOVERED) || trx_state_eq(trx, TRX_STATE_PREPARED)); mutex_exit(&trx->mutex); @@ -940,7 +942,7 @@ public: /** Takes MVCC snapshot. - To reduce malloc probablility we reserver rw_trx_hash.size() + 32 elements + To reduce malloc probablility we reserve rw_trx_hash.size() + 32 elements in ids. For details about get_rw_trx_hash_version() != get_max_trx_id() spin diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h index e537bf80fe2..3aa357ab2e4 100644 --- a/storage/innobase/include/trx0trx.h +++ b/storage/innobase/include/trx0trx.h @@ -211,16 +211,13 @@ trx_recover_for_mysql( /*==================*/ XID* xid_list, /*!< in/out: prepared transactions */ uint len); /*!< in: number of slots in xid_list */ -/*******************************************************************//** -This function is used to find one X/Open XA distributed transaction -which is in the prepared state -@return trx or NULL; on match, the trx->xid will be invalidated; -note that the trx may have been committed, unless the caller is -holding lock_sys.mutex */ -trx_t * -trx_get_trx_by_xid( -/*===============*/ - XID* xid); /*!< in: X/Open XA transaction identifier */ +/** Look up an X/Open distributed transaction in XA PREPARE state. +@param[in] xid X/Open XA transaction identifier +@return transaction on match (the trx_t::xid will be invalidated); +note that the trx may have been committed before the caller acquires +trx_t::mutex +@retval NULL if no match */ +trx_t* trx_get_trx_by_xid(const XID* xid); /**********************************************************************//** If required, flushes the log to disk if we called trx_commit_for_mysql() with trx->flush_log_later == TRUE. */ @@ -467,6 +464,9 @@ Check transaction state */ ut_ad(!(t)->read_view.is_open()); \ ut_ad((t)->lock.wait_thr == NULL); \ ut_ad(UT_LIST_GET_LEN((t)->lock.trx_locks) == 0); \ + ut_ad((t)->lock.table_locks.empty()); \ + ut_ad(!(t)->autoinc_locks \ + || ib_vector_is_empty((t)->autoinc_locks)); \ ut_ad((t)->dict_operation == TRX_DICT_OP_NONE); \ } while(0) @@ -701,8 +701,8 @@ so without holding any mutex. The following are exceptions to this: * trx_rollback_resurrected() may access resurrected (connectionless) transactions while the system is already processing new user -transactions. The trx_sys.mutex prevents a race condition between it -and lock_trx_release_locks() [invoked by trx_commit()]. +transactions. The trx_sys.mutex and trx->is_recovered prevent +a race condition between it and trx_commit(). * trx_print_low() may access transactions not associated with the current thread. The caller must be holding lock_sys.mutex. @@ -713,7 +713,7 @@ must not be modified without holding trx->mutex. * The locking code (in particular, lock_deadlock_recursive() and lock_rec_convert_impl_to_expl()) will access transactions associated to other connections. The locks of transactions are protected by -lock_sys.mutex and sometimes by trx->mutex. */ +lock_sys.mutex (insertions also by trx->mutex). */ /** Represents an instance of rollback segment along with its state variables.*/ struct trx_undo_ptr_t { @@ -837,26 +837,19 @@ public: ACTIVE->COMMITTED is possible when the transaction is in rw_trx_hash. - Transitions to COMMITTED are protected by both lock_sys.mutex - and trx->mutex. - - NOTE: Some of these state change constraints are an overkill, - currently only required for a consistent view for printing stats. - This unnecessarily adds a huge cost for the general case. */ - + Transitions to COMMITTED are protected by trx_t::mutex. */ trx_state_t state; + /** whether this is a recovered transaction that should be + rolled back by trx_rollback_or_clean_recovered(). + Protected by trx_t::mutex for transactions that are in trx_sys. */ + bool is_recovered; ReadView read_view; /*!< consistent read view used in the transaction, or NULL if not yet set */ trx_lock_t lock; /*!< Information about the transaction locks and state. Protected by - trx->mutex or lock_sys.mutex - or both */ - bool is_recovered; /*!< 0=normal transaction, - 1=recovered, must be rolled back, - protected by trx_sys.mutex when - trx is in rw_trx_hash */ - + lock_sys.mutex (insertions also + by trx_t::mutex). */ /* These fields are not protected by any mutex. */ const char* op_info; /*!< English text describing the @@ -1116,6 +1109,12 @@ public: return flush_observer; } + /** Transition to committed state, to release implicit locks. */ + inline void commit_state(); + + /** Release any explicit locks of a committing transaction. */ + inline void release_locks(); + bool is_referenced() { diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index 666c74cba74..90049ad10a7 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -4740,7 +4740,7 @@ lock_trx_table_locks_find( { bool found = false; - trx_mutex_enter(trx); + ut_ad(trx_mutex_own(trx)); for (lock_list::const_iterator it = trx->lock.table_locks.begin(), end = trx->lock.table_locks.end(); it != end; ++it) { @@ -4763,8 +4763,6 @@ lock_trx_table_locks_find( ut_a(lock->un_member.tab_lock.table != NULL); } - trx_mutex_exit(trx); - return(found); } @@ -4785,25 +4783,23 @@ lock_table_queue_validate( lock != NULL; lock = UT_LIST_GET_NEXT(un_member.tab_lock.locks, lock)) { - /* Transaction state may change from ACTIVE to PREPARED. - State change to COMMITTED is not possible while we are - holding lock_sys.mutex: it is done by lock_trx_release_locks() - under lock_sys.mutex protection. - Transaction in NOT_STARTED state cannot hold locks, and - lock->trx->state can only move to NOT_STARTED from COMMITTED. */ + /* lock->trx->state cannot change from or to NOT_STARTED + while we are holding the trx_sys.mutex. It may change + from ACTIVE or PREPARED to PREPARED or COMMITTED. */ + trx_mutex_enter(lock->trx); check_trx_state(lock->trx); - if (!lock_get_wait(lock)) { - + if (lock->trx->state == TRX_STATE_COMMITTED_IN_MEMORY) { + } else if (!lock_get_wait(lock)) { ut_a(!lock_table_other_has_incompatible( lock->trx, 0, table, lock_get_mode(lock))); } else { - ut_a(lock_table_has_to_wait_in_queue(lock)); } ut_a(lock_trx_table_locks_find(lock->trx, lock)); + trx_mutex_exit(lock->trx); } return(TRUE); @@ -4850,42 +4846,41 @@ lock_rec_queue_validate( lock != NULL; lock = lock_rec_get_next_const(heap_no, lock)) { - ut_ad(!trx_is_ac_nl_ro(lock->trx)); + ut_ad(!index || lock->index == index); - if (lock_get_wait(lock)) { - ut_a(lock_rec_has_to_wait_in_queue(lock)); - } + trx_mutex_enter(lock->trx); + ut_ad(!trx_is_ac_nl_ro(lock->trx)); + ut_ad(trx_state_eq(lock->trx, + TRX_STATE_COMMITTED_IN_MEMORY) + || !lock_get_wait(lock) + || lock_rec_has_to_wait_in_queue(lock)); + trx_mutex_exit(lock->trx); + } - if (index != NULL) { - ut_a(lock->index == index); - } +func_exit: + if (!locked_lock_trx_sys) { + lock_mutex_exit(); } - goto func_exit; + return true; } ut_ad(page_rec_is_leaf(rec)); + ut_ad(lock_mutex_own()); - if (index == NULL) { - - /* Nothing we can do */ - - } else if (dict_index_is_clust(index)) { - /* Unlike the non-debug code, this invariant can only succeed - if the check and assertion are covered by the lock mutex. */ - - const trx_id_t impl_trx_id = lock_clust_rec_some_has_impl( - rec, index, offsets); - - const trx_t *impl_trx = impl_trx_id - ? trx_sys.find(current_trx(), impl_trx_id, false) - : 0; + const trx_id_t impl_trx_id = index && index->is_primary() + ? lock_clust_rec_some_has_impl(rec, index, offsets) + : 0; - ut_ad(lock_mutex_own()); - /* impl_trx cannot be committed until lock_mutex_exit() - because lock_trx_release_locks() acquires lock_sys.mutex */ + if (trx_t *impl_trx = impl_trx_id + ? trx_sys.find(current_trx(), impl_trx_id, false) + : 0) { + /* impl_trx could have been committed before we + acquire its mutex, but not thereafter. */ - if (!impl_trx) { + mutex_enter(&impl_trx->mutex); + ut_ad(impl_trx->state != TRX_STATE_NOT_STARTED); + if (impl_trx->state == TRX_STATE_COMMITTED_IN_MEMORY) { } else if (const lock_t* other_lock = lock_rec_other_has_expl_req( LOCK_S, block, true, heap_no, @@ -4927,6 +4922,8 @@ lock_rec_queue_validate( ut_ad(lock_rec_has_expl(LOCK_X | LOCK_REC_NOT_GAP, block, heap_no, impl_trx)); } + + mutex_exit(&impl_trx->mutex); } for (lock = lock_rec_get_first(lock_sys.rec_hash, block, heap_no); @@ -4971,12 +4968,7 @@ lock_rec_queue_validate( ut_ad(innodb_lock_schedule_algorithm == INNODB_LOCK_SCHEDULE_ALGORITHM_FCFS || lock_queue_validate(lock)); -func_exit: - if (!locked_lock_trx_sys) { - lock_mutex_exit(); - } - - return(TRUE); + goto func_exit; } /*********************************************************************//** @@ -5398,30 +5390,24 @@ lock_rec_convert_impl_to_expl_for_trx( trx_t* trx, /*!< in/out: active transaction */ ulint heap_no)/*!< in: rec heap number to lock */ { - ut_ad(trx->is_referenced()); ut_ad(page_rec_is_leaf(rec)); ut_ad(!rec_is_metadata(rec, index)); DEBUG_SYNC_C("before_lock_rec_convert_impl_to_expl_for_trx"); - lock_mutex_enter(); - + trx_mutex_enter(trx); + ut_ad(trx->is_referenced()); ut_ad(!trx_state_eq(trx, TRX_STATE_NOT_STARTED)); if (!trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY) && !lock_rec_has_expl(LOCK_X | LOCK_REC_NOT_GAP, block, heap_no, trx)) { - - ulint type_mode; - - type_mode = (LOCK_REC | LOCK_X | LOCK_REC_NOT_GAP); - - lock_rec_add_to_queue( - type_mode, block, heap_no, index, trx, FALSE); + lock_rec_add_to_queue(LOCK_REC | LOCK_X | LOCK_REC_NOT_GAP, + block, heap_no, index, trx, true); } lock_mutex_exit(); - + trx_mutex_exit(trx); trx->release_reference(); DEBUG_SYNC_C("after_lock_rec_convert_impl_to_expl_for_trx"); @@ -5444,13 +5430,17 @@ static my_bool lock_rec_other_trx_holds_expl_callback( mutex_enter(&element->mutex); if (element->trx) { - lock_t *expl_lock= lock_rec_has_expl(LOCK_S | LOCK_REC_NOT_GAP, arg->block, - arg->heap_no, element->trx); + trx_mutex_enter(element->trx); + ut_ad(element->trx->state != TRX_STATE_NOT_STARTED); + lock_t *expl_lock= element->trx->state == TRX_STATE_COMMITTED_IN_MEMORY + ? NULL : lock_rec_has_expl(LOCK_S | LOCK_REC_NOT_GAP, arg->block, + arg->heap_no, element->trx); /* An explicit lock is held by trx other than the trx holding the implicit lock. */ ut_ad(!expl_lock || expl_lock->trx == arg->impl_trx); + trx_mutex_exit(element->trx); } mutex_exit(&element->mutex); return 0; @@ -5480,9 +5470,6 @@ static void lock_rec_other_trx_holds_expl(trx_t *caller_trx, trx_t *trx, ut_ad(!page_rec_is_metadata(rec)); lock_mutex_enter(); ut_ad(trx->is_referenced()); - /* Prevent a data race with trx_prepare(), which could change the - state from ACTIVE to PREPARED. Other state changes should be - blocked by lock_mutex_own() and trx->is_referenced(). */ trx_mutex_enter(trx); const trx_state_t state = trx->state; trx_mutex_exit(trx); @@ -6248,89 +6235,24 @@ lock_unlock_table_autoinc( } } -/*********************************************************************//** -Releases a transaction's locks, and releases possible other transactions -waiting because of these locks. Change the state of the transaction to -TRX_STATE_COMMITTED_IN_MEMORY. */ -void -lock_trx_release_locks( -/*===================*/ - trx_t* trx) /*!< in/out: transaction */ +/** Release the explicit locks of a committing transaction, +and release possible other transactions waiting because of these locks. */ +void lock_trx_release_locks(trx_t* trx) { - check_trx_state(trx); - ut_ad(trx_state_eq(trx, TRX_STATE_PREPARED) - || trx_state_eq(trx, TRX_STATE_PREPARED_RECOVERED) - || trx_state_eq(trx, TRX_STATE_ACTIVE)); - - bool release_lock = UT_LIST_GET_LEN(trx->lock.trx_locks) > 0; - - /* Don't take lock_sys.mutex if trx didn't acquire any lock. */ - if (release_lock) { - - /* The transition of trx->state to TRX_STATE_COMMITTED_IN_MEMORY - is protected by both the lock_sys.mutex and the trx->mutex. */ - lock_mutex_enter(); - } - - /* The following assignment makes the transaction committed in memory - and makes its changes to data visible to other transactions. - NOTE that there is a small discrepancy from the strict formal - visibility rules here: a human user of the database can see - modifications made by another transaction T even before the necessary - log segment has been flushed to the disk. If the database happens to - crash before the flush, the user has seen modifications from T which - will never be a committed transaction. However, any transaction T2 - which sees the modifications of the committing transaction T, and - which also itself makes modifications to the database, will get an lsn - larger than the committing transaction T. In the case where the log - flush fails, and T never gets committed, also T2 will never get - committed. */ - - /*--------------------------------------*/ - trx_mutex_enter(trx); - trx->state = TRX_STATE_COMMITTED_IN_MEMORY; - trx_mutex_exit(trx); - /*--------------------------------------*/ - - if (trx->is_referenced()) { - - ut_a(release_lock); - - lock_mutex_exit(); - - while (trx->is_referenced()) { - - DEBUG_SYNC_C("waiting_trx_is_not_referenced"); - - /** Doing an implicit to explicit conversion - should not be expensive. */ - ut_delay(srv_spin_wait_delay); - } - - lock_mutex_enter(); - } - - ut_ad(!trx->is_referenced()); - - if (release_lock) { - - lock_release(trx); - - lock_mutex_exit(); - } + ut_ad(UT_LIST_GET_LEN(trx->lock.trx_locks)); + lock_mutex_enter(); + lock_release(trx); trx->lock.n_rec_locks = 0; - /* We don't remove the locks one by one from the vector for efficiency reasons. We simply reset it because we would have released all the locks anyway. */ trx->lock.table_locks.clear(); - ut_a(UT_LIST_GET_LEN(trx->lock.trx_locks) == 0); - ut_a(ib_vector_is_empty(trx->autoinc_locks)); - ut_a(trx->lock.table_locks.empty()); - + ut_ad(UT_LIST_GET_LEN(trx->lock.trx_locks) == 0); + ut_ad(ib_vector_is_empty(trx->autoinc_locks)); + lock_mutex_exit(); mem_heap_empty(trx->lock.lock_heap); } @@ -6403,21 +6325,26 @@ static my_bool lock_table_locks_lookup(rw_trx_hash_element_t *element, mutex_enter(&element->mutex); if (element->trx) { + trx_mutex_enter(element->trx); check_trx_state(element->trx); - for (const lock_t *lock= UT_LIST_GET_FIRST(element->trx->lock.trx_locks); - lock != NULL; - lock= UT_LIST_GET_NEXT(trx_locks, lock)) + if (element->trx->state != TRX_STATE_COMMITTED_IN_MEMORY) { - ut_ad(lock->trx == element->trx); - if (lock_get_type_low(lock) == LOCK_REC) + for (const lock_t *lock= UT_LIST_GET_FIRST(element->trx->lock.trx_locks); + lock != NULL; + lock= UT_LIST_GET_NEXT(trx_locks, lock)) { - ut_ad(!dict_index_is_online_ddl(lock->index) || - dict_index_is_clust(lock->index)); - ut_ad(lock->index->table != table); + ut_ad(lock->trx == element->trx); + if (lock_get_type_low(lock) == LOCK_REC) + { + ut_ad(!dict_index_is_online_ddl(lock->index) || + lock->index->is_primary()); + ut_ad(lock->index->table != table); + } + else + ut_ad(lock->un_member.tab_lock.table != table); } - else - ut_ad(lock->un_member.tab_lock.table != table); } + trx_mutex_exit(element->trx); } mutex_exit(&element->mutex); return 0; @@ -6585,7 +6512,7 @@ DeadlockChecker::start_print() if (srv_print_all_deadlocks) { ib::info() << "Transactions deadlock detected, dumping" - << " detailed information."; + " detailed information."; } } diff --git a/storage/innobase/row/row0vers.cc b/storage/innobase/row/row0vers.cc index f327dce121b..04237b8f7a4 100644 --- a/storage/innobase/row/row0vers.cc +++ b/storage/innobase/row/row0vers.cc @@ -76,7 +76,8 @@ index record. @param[in] index secondary index @param[in] offsets rec_get_offsets(rec, index) @param[in,out] mtr mini-transaction -@return the active transaction; trx->release_reference() must be invoked +@return the active transaction; state must be rechecked after +trx_mutex_enter(), and trx->release_reference() must be invoked @retval NULL if the record was committed */ UNIV_INLINE trx_t* @@ -90,9 +91,6 @@ row_vers_impl_x_locked_low( mtr_t* mtr) { trx_id_t trx_id; - ulint comp; - ulint rec_del; - const rec_t* version; rec_t* prev_version = NULL; ulint* clust_offsets; mem_heap_t* heap; @@ -144,12 +142,12 @@ row_vers_impl_x_locked_low( } } - comp = page_rec_is_comp(rec); + const ulint comp = page_rec_is_comp(rec); ut_ad(index->table == clust_index->table); ut_ad(!!comp == dict_table_is_comp(index->table)); ut_ad(!comp == !page_rec_is_comp(clust_rec)); - rec_del = rec_get_deleted_flag(rec, comp); + const ulint rec_del = rec_get_deleted_flag(rec, comp); if (dict_index_has_virtual(index)) { ulint n_ext; @@ -174,7 +172,7 @@ row_vers_impl_x_locked_low( modify rec, and does not necessarily have an implicit x-lock on rec. */ - for (version = clust_rec;; version = prev_version) { + for (const rec_t* version = clust_rec;; version = prev_version) { row_ext_t* ext; dtuple_t* row; dtuple_t* entry; @@ -194,16 +192,24 @@ row_vers_impl_x_locked_low( heap, &prev_version, NULL, dict_index_has_virtual(index) ? &vrow : NULL, 0); + trx_mutex_enter(trx); + const bool committed = trx_state_eq( + trx, TRX_STATE_COMMITTED_IN_MEMORY); + trx_mutex_exit(trx); + /* The oldest visible clustered index version must not be delete-marked, because we never start a transaction by inserting a delete-marked record. */ - ut_ad(prev_version - || !rec_get_deleted_flag(version, comp) - || !trx_sys.is_registered(caller_trx, trx_id)); + ut_ad(committed || prev_version + || !rec_get_deleted_flag(version, comp)); /* Free version and clust_offsets. */ mem_heap_free(old_heap); + if (committed) { + goto not_locked; + } + if (prev_version == NULL) { /* We reached the oldest visible version without @@ -223,6 +229,7 @@ row_vers_impl_x_locked_low( or updated, the leaf page record always is created with a clear delete-mark flag. (We never insert a delete-marked record.) */ +not_locked: trx->release_reference(); trx = 0; } @@ -349,14 +356,14 @@ result_check: if (trx->id != prev_trx_id) { /* prev_version was the first version modified by the trx_id transaction: no implicit x-lock */ - - trx->release_reference(); - trx = 0; - break; + goto not_locked; } } - DBUG_PRINT("info", ("Implicit lock is held by trx:" TRX_ID_FMT, trx_id)); + if (trx) { + DBUG_PRINT("info", ("Implicit lock is held by trx:" TRX_ID_FMT, + trx_id)); + } if (v_heap != NULL) { mem_heap_free(v_heap); @@ -372,7 +379,8 @@ index record. @param[in] rec secondary index record @param[in] index secondary index @param[in] offsets rec_get_offsets(rec, index) -@return the active transaction; trx->release_reference() must be invoked +@return the active transaction; state must be rechecked after +trx_mutex_enter(), and trx->release_reference() must be invoked @retval NULL if the record was committed */ trx_t* row_vers_impl_x_locked( diff --git a/storage/innobase/trx/trx0roll.cc b/storage/innobase/trx/trx0roll.cc index 65aa454808f..709ec98259c 100644 --- a/storage/innobase/trx/trx0roll.cc +++ b/storage/innobase/trx/trx0roll.cc @@ -764,6 +764,10 @@ static my_bool trx_rollback_recovered_callback(rw_trx_hash_element_t *element, mutex_enter(&element->mutex); if (trx_t *trx= element->trx) { + /* The trx->is_recovered flag and trx->state are set + atomically under the protection of the trx->mutex in + trx_t::commit_state(). We do not want to accidentally clean up + a non-recovered transaction here. */ mutex_enter(&trx->mutex); if (trx->is_recovered && trx_state_eq(trx, TRX_STATE_ACTIVE)) trx_list->push_back(trx); diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc index 2eb3161f3b7..6cbf0c273d9 100644 --- a/storage/innobase/trx/trx0trx.cc +++ b/storage/innobase/trx/trx0trx.cc @@ -460,10 +460,57 @@ void trx_free(trx_t*& trx) trx = NULL; } +/** Transition to committed state, to release implicit locks. */ +inline void trx_t::commit_state() +{ + /* This makes the transaction committed in memory and makes its + changes to data visible to other transactions. NOTE that there is a + small discrepancy from the strict formal visibility rules here: a + user of the database can see modifications made by another + transaction T even before the necessary redo log segment has been + flushed to the disk. If the database happens to crash before the + flush, the user has seen modifications from T which will never be a + committed transaction. However, any transaction T2 which sees the + modifications of the committing transaction T, and which also itself + makes modifications to the database, will get an lsn larger than the + committing transaction T. In the case where the log flush fails, and + T never gets committed, also T2 will never get committed. */ + ut_ad(trx_mutex_own(this)); + ut_ad(state != TRX_STATE_NOT_STARTED); + ut_ad(state != TRX_STATE_COMMITTED_IN_MEMORY + || (is_recovered && !UT_LIST_GET_LEN(lock.trx_locks))); + state= TRX_STATE_COMMITTED_IN_MEMORY; + + /* If the background thread trx_rollback_or_clean_recovered() + is still active then there is a chance that the rollback + thread may see this trx as COMMITTED_IN_MEMORY and goes ahead + to clean it up calling trx_cleanup_at_db_startup(). This can + happen in the case we are committing a trx here that is left + in PREPARED state during the crash. Note that commit of the + rollback of a PREPARED trx happens in the recovery thread + while the rollback of other transactions happen in the + background thread. To avoid this race we unconditionally unset + the is_recovered flag. */ + is_recovered= false; + ut_ad(id || !is_referenced()); +} + +/** Release any explicit locks of a committing transaction. */ +inline void trx_t::release_locks() +{ + DBUG_ASSERT(state == TRX_STATE_COMMITTED_IN_MEMORY); + + if (UT_LIST_GET_LEN(lock.trx_locks)) + lock_trx_release_locks(this); + else + lock.table_locks.clear(); // Work around a bug +} + /** At shutdown, frees a transaction object. */ void trx_free_at_shutdown(trx_t *trx) { + trx_mutex_enter(trx); ut_ad(trx->is_recovered); ut_a(trx_state_eq(trx, TRX_STATE_PREPARED) || trx_state_eq(trx, TRX_STATE_PREPARED_RECOVERED) @@ -477,21 +524,16 @@ trx_free_at_shutdown(trx_t *trx) && !srv_undo_sources && srv_fast_shutdown)))); ut_a(trx->magic_n == TRX_MAGIC_N); - lock_trx_release_locks(trx); + trx->commit_state(); + trx_mutex_exit(trx); + trx->release_locks(); trx_undo_free_at_shutdown(trx); ut_a(!trx->read_only); DBUG_LOG("trx", "Free prepared: " << trx); trx->state = TRX_STATE_NOT_STARTED; - - /* Undo trx_resurrect_table_locks(). */ - lock_trx_lock_list_init(&trx->lock.trx_locks); - - /* Note: This vector is not guaranteed to be empty because the - transaction was never committed and therefore lock_trx_release() - was not called. */ - trx->lock.table_locks.clear(); + ut_ad(!UT_LIST_GET_LEN(trx->lock.trx_locks)); trx->id = 0; trx_free(trx); @@ -1308,8 +1350,8 @@ trx_commit_in_memory( /* Note: We are asserting without holding the lock mutex. But that is OK because this transaction is not waiting and cannot - be rolled back and no new locks can (or should not) be added - becuase it is flagged as a non-locking read-only transaction. */ + be rolled back and no new locks can (or should) be added + because it is flagged as a non-locking read-only transaction. */ ut_a(UT_LIST_GET_LEN(trx->lock.trx_locks) == 0); @@ -1327,30 +1369,35 @@ trx_commit_in_memory( DBUG_LOG("trx", "Autocommit in memory: " << trx); trx->state = TRX_STATE_NOT_STARTED; } else { - if (trx->id > 0) { - /* For consistent snapshot, we need to remove current - transaction from rw_trx_hash before doing commit and - releasing locks. */ + trx_mutex_enter(trx); + trx->commit_state(); + trx_mutex_exit(trx); + + if (trx->id) { trx_sys.deregister_rw(trx); - } - lock_trx_release_locks(trx); - ut_ad(trx->read_only || !trx->rsegs.m_redo.rseg || trx->id); + /* Wait for any implicit-to-explicit lock + conversions to cease, so that there will be no + race condition in lock_release(). */ + while (UNIV_UNLIKELY(trx->is_referenced())) { + ut_delay(srv_spin_wait_delay); + } - /* Remove the transaction from the list of active - transactions now that it no longer holds any user locks. */ + trx->release_locks(); + trx->id = 0; + } else { + ut_ad(trx->read_only || !trx->rsegs.m_redo.rseg); + trx->release_locks(); + } - ut_ad(trx_state_eq(trx, TRX_STATE_COMMITTED_IN_MEMORY)); DEBUG_SYNC_C("after_trx_committed_in_memory"); - if (trx->read_only || trx->rsegs.m_redo.rseg == NULL) { + if (trx->read_only || !trx->rsegs.m_redo.rseg) { MONITOR_INC(MONITOR_TRX_RO_COMMIT); } else { trx_update_mod_tables_timestamp(trx); MONITOR_INC(MONITOR_TRX_RW_COMMIT); } - - trx->id = 0; } ut_ad(!trx->rsegs.m_redo.undo); @@ -2166,7 +2213,7 @@ int trx_recover_for_mysql(XID *xid_list, uint len) struct trx_get_trx_by_xid_callback_arg { - XID *xid; + const XID *xid; trx_t *trx; }; @@ -2178,6 +2225,7 @@ static my_bool trx_get_trx_by_xid_callback(rw_trx_hash_element_t *element, mutex_enter(&element->mutex); if (trx_t *trx= element->trx) { + trx_mutex_enter(trx); if (trx->is_recovered && (trx_state_eq(trx, TRX_STATE_PREPARED) || trx_state_eq(trx, TRX_STATE_PREPARED_RECOVERED)) && @@ -2194,23 +2242,19 @@ static my_bool trx_get_trx_by_xid_callback(rw_trx_hash_element_t *element, arg->trx= trx; found= 1; } + trx_mutex_exit(trx); } mutex_exit(&element->mutex); return found; } - -/** - Finds PREPARED XA transaction by xid. - - trx may have been committed, unless the caller is holding lock_sys.mutex. - - @param[in] xid X/Open XA transaction identifier - - @return trx or NULL; on match, the trx->xid will be invalidated; -*/ - -trx_t *trx_get_trx_by_xid(XID *xid) +/** Look up an X/Open distributed transaction in XA PREPARE state. +@param[in] xid X/Open XA transaction identifier +@return transaction on match (the trx_t::xid will be invalidated); +note that the trx may have been committed before the caller acquires +trx_t::mutex +@retval NULL if no match */ +trx_t* trx_get_trx_by_xid(const XID* xid) { trx_get_trx_by_xid_callback_arg arg= { xid, 0 }; diff --git a/storage/innobase/trx/trx0undo.cc b/storage/innobase/trx/trx0undo.cc index 14f4e9b31fe..7a0accf889f 100644 --- a/storage/innobase/trx/trx0undo.cc +++ b/storage/innobase/trx/trx0undo.cc @@ -1634,7 +1634,7 @@ trx_undo_free_at_shutdown(trx_t *trx) TRX_STATE_COMMITTED_IN_MEMORY)); /* fall through */ case TRX_UNDO_ACTIVE: - /* lock_trx_release_locks() assigns + /* trx_t::commit_state() assigns trx->state = TRX_STATE_COMMITTED_IN_MEMORY. */ ut_a(!srv_was_started || srv_read_only_mode @@ -1661,7 +1661,7 @@ trx_undo_free_at_shutdown(trx_t *trx) TRX_STATE_COMMITTED_IN_MEMORY)); /* fall through */ case TRX_UNDO_ACTIVE: - /* lock_trx_release_locks() assigns + /* trx_t::commit_state() assigns trx->state = TRX_STATE_COMMITTED_IN_MEMORY. */ ut_a(!srv_was_started || srv_read_only_mode diff --git a/storage/maria/ha_maria.cc b/storage/maria/ha_maria.cc index 025477366a5..57537a69082 100644 --- a/storage/maria/ha_maria.cc +++ b/storage/maria/ha_maria.cc @@ -2785,7 +2785,20 @@ static void reset_thd_trn(THD *thd, MARIA_HA *first_table) THD_TRN= NULL; for (MARIA_HA *table= first_table; table ; table= table->trn_next) + { _ma_reset_trn_for_table(table); + + /* + If table has changed by this statement, invalidate it from the query + cache + */ + if (table->row_changes != table->start_row_changes) + { + table->start_row_changes= table->row_changes; + DBUG_ASSERT(table->s->chst_invalidator != NULL); + (*table->s->chst_invalidator)(table->s->data_file_name.str); + } + } DBUG_VOID_RETURN; } @@ -3252,7 +3265,10 @@ static int maria_commit(handlerton *hton __attribute__ ((unused)), THD *thd, bool all) { TRN *trn= THD_TRN; + int res; + MARIA_HA *used_instances= (MARIA_HA*) trn->used_instances; DBUG_ENTER("maria_commit"); + trnman_reset_locked_tables(trn, 0); trnman_set_flags(trn, trnman_get_flags(trn) & ~TRN_STATE_INFO_LOGGED); @@ -3260,8 +3276,9 @@ static int maria_commit(handlerton *hton __attribute__ ((unused)), if ((thd->variables.option_bits & (OPTION_NOT_AUTOCOMMIT | OPTION_BEGIN)) && !all) DBUG_RETURN(0); // end of statement - reset_thd_trn(thd, (MARIA_HA*) trn->used_instances); - DBUG_RETURN(ma_commit(trn)); // end of transaction + res= ma_commit(trn); + reset_thd_trn(thd, used_instances); + DBUG_RETURN(res); } diff --git a/storage/maria/maria_def.h b/storage/maria/maria_def.h index 2f39c4dadef..c6c5d008e38 100644 --- a/storage/maria/maria_def.h +++ b/storage/maria/maria_def.h @@ -641,6 +641,7 @@ struct st_maria_handler invalidator_by_filename invalidator; /* query cache invalidator */ ulonglong last_auto_increment; /* auto value at start of statement */ ulonglong row_changes; /* Incremented for each change */ + ulonglong start_row_changes; /* Row changes since start trans */ ulong this_unique; /* uniq filenumber or thread */ ulong last_unique; /* last unique number */ ulong this_loop; /* counter for this open */ |