summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarko Mäkelä <marko.makela@mariadb.com>2019-09-04 17:52:04 +0300
committerMarko Mäkelä <marko.makela@mariadb.com>2019-09-04 17:52:04 +0300
commit537f8594a60a1e09d6da0933b55764e0f8abbf5c (patch)
tree628c68bccb01538df826045ba6f3aaa45c29b93a
parent647d5b243020b799fd7112a801965f5780b86349 (diff)
parentf605ce08b5c0e6ed6907d0639bcc5b5630e9b40a (diff)
downloadmariadb-git-537f8594a60a1e09d6da0933b55764e0f8abbf5c.tar.gz
Merge 10.2 into 10.3
m---------libmariadb0
-rw-r--r--mysql-test/main/default.result20
-rw-r--r--mysql-test/main/default.test15
-rw-r--r--mysql-test/main/function_defaults.result52
-rw-r--r--mysql-test/main/function_defaults.test48
-rw-r--r--mysql-test/main/invisible_field.result2
-rw-r--r--mysql-test/suite/galera/disabled.def3
-rw-r--r--mysql-test/suite/innodb/r/recovery_shutdown.result8
-rw-r--r--mysql-test/suite/innodb/t/recovery_shutdown.test15
-rw-r--r--mysql-test/suite/rpl/disabled.def1
-rw-r--r--mysql-test/suite/rpl/r/mdev_17588.result14
-rw-r--r--mysql-test/suite/rpl/t/mdev_17588.test23
-rw-r--r--sql/field.cc27
-rw-r--r--sql/field.h23
-rw-r--r--sql/handler.h4
-rw-r--r--sql/item.cc4
-rw-r--r--sql/sql_base.cc21
-rw-r--r--sql/sql_insert.cc7
-rw-r--r--sql/sql_table.cc4
-rw-r--r--sql/sql_update.cc31
-rw-r--r--sql/table.cc28
-rw-r--r--sql/table.h3
-rw-r--r--storage/innobase/include/lock0lock.h12
-rw-r--r--storage/innobase/include/row0vers.h3
-rw-r--r--storage/innobase/include/trx0sys.h4
-rw-r--r--storage/innobase/include/trx0trx.h53
-rw-r--r--storage/innobase/lock/lock0lock.cc217
-rw-r--r--storage/innobase/row/row0vers.cc40
-rw-r--r--storage/innobase/trx/trx0roll.cc4
-rw-r--r--storage/innobase/trx/trx0trx.cc118
-rw-r--r--storage/innobase/trx/trx0undo.cc4
-rw-r--r--storage/maria/ha_maria.cc21
-rw-r--r--storage/maria/maria_def.h1
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> &not_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 */