diff options
author | Marko Mäkelä <marko.makela@mariadb.com> | 2020-07-21 17:33:16 +0300 |
---|---|---|
committer | Marko Mäkelä <marko.makela@mariadb.com> | 2020-07-21 17:33:16 +0300 |
commit | 4ec032b492de5c392f66c9c1764cbf72442ed3a9 (patch) | |
tree | 0d9d0173f285df28d525fab7fbefd6879174e4a7 | |
parent | c89366866bca2df46b0592719a1f6b6dabf470cb (diff) | |
parent | b1538f4d60bf30de00417a7c3f948d2de654fcb3 (diff) | |
download | mariadb-git-4ec032b492de5c392f66c9c1764cbf72442ed3a9.tar.gz |
Merge 10.4 into 10.5
24 files changed, 647 insertions, 182 deletions
diff --git a/mysql-test/suite/innodb/r/instant_alter_charset.result b/mysql-test/suite/innodb/r/instant_alter_charset.result index cbb49819bfe..9e7dab8f7b4 100644 --- a/mysql-test/suite/innodb/r/instant_alter_charset.result +++ b/mysql-test/suite/innodb/r/instant_alter_charset.result @@ -1929,3 +1929,66 @@ KEY a_idx(a(1)) INSERT INTO t VALUES (1, 'something in the air'); ALTER TABLE t MODIFY a text CHARSET utf8mb4; DROP TABLE t; +# +# MDEV-22899: Assertion `field->col->is_binary() || field->prefix_len % field->col->mbmaxlen == 0' failed in dict_index_add_to_cache +# +CREATE TABLE t1 ( +a text CHARACTER SET utf8 DEFAULT NULL, +KEY a_key (a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; +CREATE TABLE t1 ( +a text CHARACTER SET utf8 DEFAULT NULL, +b int, +KEY a_key (b, a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; +CREATE TABLE t1 ( +a char(200) CHARACTER SET utf8 DEFAULT NULL, +KEY a_key (a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; +CREATE TABLE t1 ( +a char(200) CHARACTER SET utf8 DEFAULT NULL, +b int, +KEY a_key (b, a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; +CREATE TABLE t1 ( +a varchar(200) CHARACTER SET utf8 DEFAULT NULL, +KEY a_key (a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; +CREATE TABLE t1 ( +a varchar(200) CHARACTER SET utf8 DEFAULT NULL, +b int, +KEY a_key (b, a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; +CREATE TABLE t1 ( +a varchar(2000) CHARACTER SET utf8 DEFAULT NULL, +KEY a_key (a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; +CREATE TABLE t1 ( +a varchar(2000) CHARACTER SET utf8 DEFAULT NULL, +b int, +KEY a_key (b, a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; diff --git a/mysql-test/suite/innodb/t/instant_alter_charset.test b/mysql-test/suite/innodb/t/instant_alter_charset.test index 270db2d6462..b14c7861c25 100644 --- a/mysql-test/suite/innodb/t/instant_alter_charset.test +++ b/mysql-test/suite/innodb/t/instant_alter_charset.test @@ -730,3 +730,76 @@ INSERT INTO t VALUES (1, 'something in the air'); ALTER TABLE t MODIFY a text CHARSET utf8mb4; DROP TABLE t; + + +--echo # +--echo # MDEV-22899: Assertion `field->col->is_binary() || field->prefix_len % field->col->mbmaxlen == 0' failed in dict_index_add_to_cache +--echo # + +CREATE TABLE t1 ( + a text CHARACTER SET utf8 DEFAULT NULL, + KEY a_key (a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; + +CREATE TABLE t1 ( + a text CHARACTER SET utf8 DEFAULT NULL, + b int, + KEY a_key (b, a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; + +CREATE TABLE t1 ( + a char(200) CHARACTER SET utf8 DEFAULT NULL, + KEY a_key (a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; + +CREATE TABLE t1 ( + a char(200) CHARACTER SET utf8 DEFAULT NULL, + b int, + KEY a_key (b, a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; + +CREATE TABLE t1 ( + a varchar(200) CHARACTER SET utf8 DEFAULT NULL, + KEY a_key (a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; + +CREATE TABLE t1 ( + a varchar(200) CHARACTER SET utf8 DEFAULT NULL, + b int, + KEY a_key (b, a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; + +CREATE TABLE t1 ( + a varchar(2000) CHARACTER SET utf8 DEFAULT NULL, + KEY a_key (a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; + +CREATE TABLE t1 ( + a varchar(2000) CHARACTER SET utf8 DEFAULT NULL, + b int, + KEY a_key (b, a(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; +INSERT INTO t1 VALUES (); +ALTER TABLE t1 MODIFY a text DEFAULT NULL; +DROP TABLE t1; diff --git a/mysql-test/suite/rpl/r/parallel_backup.result b/mysql-test/suite/rpl/r/parallel_backup.result new file mode 100644 index 00000000000..d87c61f2d0f --- /dev/null +++ b/mysql-test/suite/rpl/r/parallel_backup.result @@ -0,0 +1,40 @@ +include/master-slave.inc +[connection master] +# +# MDEV-21953: deadlock between BACKUP STAGE BLOCK_COMMIT and parallel +# replication +# +connection master; +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE = innodb; +connection slave; +include/stop_slave.inc +SET @old_parallel_threads= @@GLOBAL.slave_parallel_threads; +SET @old_parallel_mode = @@GLOBAL.slave_parallel_mode; +SET @@global.slave_parallel_threads= 2; +SET @@global.slave_parallel_mode = 'optimistic'; +connection master; +INSERT INTO t1 VALUES (1); +INSERT INTO t1 VALUES (2); +connect aux_slave,127.0.0.1,root,,test,$SLAVE_MYPORT,; +BEGIN; +INSERT INTO t1 VALUES (1); +connection slave; +include/start_slave.inc +connection aux_slave; +connect backup_slave,127.0.0.1,root,,test,$SLAVE_MYPORT,; +BACKUP STAGE START; +BACKUP STAGE BLOCK_COMMIT; +connection aux_slave; +ROLLBACK; +connection backup_slave; +BACKUP STAGE END; +connection slave; +include/diff_tables.inc [master:t1,slave:t1] +connection slave; +include/stop_slave.inc +SET @@global.slave_parallel_threads= @old_parallel_threads; +SET @@global.slave_parallel_mode = @old_parallel_mode; +include/start_slave.inc +connection server_1; +DROP TABLE t1; +include/rpl_end.inc diff --git a/mysql-test/suite/rpl/t/parallel_backup.test b/mysql-test/suite/rpl/t/parallel_backup.test new file mode 100644 index 00000000000..6ed182c024b --- /dev/null +++ b/mysql-test/suite/rpl/t/parallel_backup.test @@ -0,0 +1,75 @@ +--source include/have_innodb.inc +# The test is not format specific, MIXED is required to optimize testing time +--source include/have_binlog_format_mixed.inc +--source include/master-slave.inc + +--echo # +--echo # MDEV-21953: deadlock between BACKUP STAGE BLOCK_COMMIT and parallel +--echo # replication +--echo # + +--connection master +CREATE TABLE t1 (a INT PRIMARY KEY) ENGINE = innodb; + +--sync_slave_with_master +--source include/stop_slave.inc +SET @old_parallel_threads= @@GLOBAL.slave_parallel_threads; +SET @old_parallel_mode = @@GLOBAL.slave_parallel_mode; +SET @@global.slave_parallel_threads= 2; +SET @@global.slave_parallel_mode = 'optimistic'; + +--connection master +INSERT INTO t1 VALUES (1); +INSERT INTO t1 VALUES (2); +--save_master_pos + +# The plot: +# Block the 1st of two workers and, at waiting-for-prior-commit by the 2nd, +# issue BACKUP commands. +# BLOCK_COMMIT may hang so it is --send. +# Release the 1st worker to observe a deadlock unless its fixed. + +--connect (aux_slave,127.0.0.1,root,,test,$SLAVE_MYPORT,) +BEGIN; +# block the 1st worker and wait for the 2nd ready to commit +INSERT INTO t1 VALUES (1); + +--connection slave +--source include/start_slave.inc + +--connection aux_slave +--let $wait_condition= SELECT COUNT(*) > 0 FROM information_schema.processlist WHERE state = "Waiting for prior transaction to commit" +--source include/wait_condition.inc + +# While the 1st worker is locked out run backup +--connect (backup_slave,127.0.0.1,root,,test,$SLAVE_MYPORT,) +BACKUP STAGE START; +--send BACKUP STAGE BLOCK_COMMIT + +# release the 1st work +--connection aux_slave +--sleep 1 +ROLLBACK; + +--connection backup_slave +--reap +BACKUP STAGE END; + +--connection slave +--sync_with_master + +--let $diff_tables= master:t1,slave:t1 +--source include/diff_tables.inc + + +# Clean up. +--connection slave +--source include/stop_slave.inc +SET @@global.slave_parallel_threads= @old_parallel_threads; +SET @@global.slave_parallel_mode = @old_parallel_mode; +--source include/start_slave.inc + +--connection server_1 +DROP TABLE t1; + +--source include/rpl_end.inc diff --git a/mysql-test/suite/sql_sequence/kill.result b/mysql-test/suite/sql_sequence/kill.result new file mode 100644 index 00000000000..6d966254de3 --- /dev/null +++ b/mysql-test/suite/sql_sequence/kill.result @@ -0,0 +1,12 @@ +# +# MDEV-16929 Assertion ... in close_thread_tables upon killing connection +# running SHOW on sequence +# +CREATE SEQUENCE s ENGINE=InnoDB; +RENAME TABLE s TO s1; +connect con1,localhost,root,,test; +SHOW CREATE SEQUENCE s1; +connection default; +KILL thread_id; +connection default; +drop sequence s1; diff --git a/mysql-test/suite/sql_sequence/kill.test b/mysql-test/suite/sql_sequence/kill.test new file mode 100644 index 00000000000..9caebc57f12 --- /dev/null +++ b/mysql-test/suite/sql_sequence/kill.test @@ -0,0 +1,20 @@ +--source include/have_innodb.inc + +--echo # +--echo # MDEV-16929 Assertion ... in close_thread_tables upon killing connection +--echo # running SHOW on sequence +--echo # + +CREATE SEQUENCE s ENGINE=InnoDB; +RENAME TABLE s TO s1; +--connect (con1,localhost,root,,test) +--let $conid= `SELECT CONNECTION_ID()` +--send + SHOW CREATE SEQUENCE s1; +--connection default +--replace_result $conid thread_id +--eval KILL $conid + +# Cleanup +--connection default +drop sequence s1; diff --git a/mysql-test/suite/versioning/r/update.result b/mysql-test/suite/versioning/r/update.result index f7901d11d2a..cd26c341113 100644 --- a/mysql-test/suite/versioning/r/update.result +++ b/mysql-test/suite/versioning/r/update.result @@ -319,3 +319,34 @@ create or replace table t1 (f point, key(f)) with system versioning engine=myisa update t1 set f = null where f = 'foo'; ERROR 22003: Cannot get geometry object from data you send to the GEOMETRY field drop table t1; +# +# MDEV-22061 InnoDB: Assertion of missing row in sec index row_start upon REPLACE on a system-versioned table +# +create or replace table t1 ( +a int, +b int, +row_start bigint(20) unsigned generated always as row start, +row_end bigint(20) unsigned generated always as row end, +unique key (b,row_end), +key (row_start), +period for system_time (row_start,row_end) +) engine=innodb with system versioning; +insert into t1 (a, b) values (1, 2); +replace into t1 (a, b) values (3, 2); +replace into t1 (a, b) values (4, 2); +drop table t1; +# +# MDEV-20661 Virtual fields are not recalculated on system fields value assignment +# +create table t1 ( +a int, +row_start SYS_DATATYPE as row start invisible, +row_end SYS_DATATYPE as row end invisible, +period for system_time (row_start, row_end), +v1 bigint unsigned as (a ^ row_start) unique, +v2 bigint unsigned as (a ^ row_end) unique +) engine=innodb with system versioning; +insert into t1 (a) values (1), (2); +update ignore t1 set a= 3; +delete history from t1; +drop table t1; diff --git a/mysql-test/suite/versioning/t/update.test b/mysql-test/suite/versioning/t/update.test index 5b0a9eb5c42..06f81ea9064 100644 --- a/mysql-test/suite/versioning/t/update.test +++ b/mysql-test/suite/versioning/t/update.test @@ -245,4 +245,45 @@ update t1 set f = null where f = 'foo'; # cleanup drop table t1; +--echo # +--echo # MDEV-22061 InnoDB: Assertion of missing row in sec index row_start upon REPLACE on a system-versioned table +--echo # +create or replace table t1 ( + a int, + b int, + row_start bigint(20) unsigned generated always as row start, + row_end bigint(20) unsigned generated always as row end, + unique key (b,row_end), + key (row_start), + period for system_time (row_start,row_end) +) engine=innodb with system versioning; + +insert into t1 (a, b) values (1, 2); +replace into t1 (a, b) values (3, 2); +replace into t1 (a, b) values (4, 2); + +# cleanup +drop table t1; + +--echo # +--echo # MDEV-20661 Virtual fields are not recalculated on system fields value assignment +--echo # + +replace_result $sys_datatype_expl SYS_DATATYPE; +eval create table t1 ( + a int, + row_start $sys_datatype_expl as row start invisible, + row_end $sys_datatype_expl as row end invisible, + period for system_time (row_start, row_end), + v1 bigint unsigned as (a ^ row_start) unique, + v2 bigint unsigned as (a ^ row_end) unique +) engine=innodb with system versioning; + +insert into t1 (a) values (1), (2); +update ignore t1 set a= 3; +delete history from t1; + +# cleanup +drop table t1; + source suite/versioning/common_finish.inc; diff --git a/sql/handler.cc b/sql/handler.cc index ace58869145..48bae864125 100644 --- a/sql/handler.cc +++ b/sql/handler.cc @@ -188,7 +188,7 @@ private: static int commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, - bool is_real_trans); + bool is_real_trans, bool rw_trans); static plugin_ref ha_default_plugin(THD *thd) @@ -1621,39 +1621,9 @@ int ha_commit_trans(THD *thd, bool all) /* rw_trans is TRUE when we in a transaction changing data */ bool rw_trans= is_real_trans && (rw_ha_count > (thd->is_current_stmt_binlog_disabled()?0U:1U)); - MDL_request mdl_request; - mdl_request.ticket= 0; DBUG_PRINT("info", ("is_real_trans: %d rw_trans: %d rw_ha_count: %d", is_real_trans, rw_trans, rw_ha_count)); - /* - We need to test maria_hton because of plugin_innodb.test that changes - the plugin table to innodb and thus plugin_load will call - mysql_close_tables() which calls trans_commit_trans() with maria_hton = 0 - */ - if (rw_trans) - { - /* - Acquire a metadata lock which will ensure that COMMIT is blocked - by an active FLUSH TABLES WITH READ LOCK (and vice versa: - COMMIT in progress blocks FTWRL). - - We allow the owner of FTWRL to COMMIT; we assume that it knows - what it does. - */ - MDL_REQUEST_INIT(&mdl_request, MDL_key::BACKUP, "", "", MDL_BACKUP_COMMIT, - MDL_EXPLICIT); - - if (!WSREP(thd) && - thd->mdl_context.acquire_lock(&mdl_request, - thd->variables.lock_wait_timeout)) - { - ha_rollback_trans(thd, all); - DBUG_RETURN(1); - } - - DEBUG_SYNC(thd, "ha_commit_trans_after_acquire_commit_lock"); - } if (rw_trans && opt_readonly && !(thd->security_ctx->master_access & PRIV_IGNORE_READ_ONLY) && @@ -1693,7 +1663,7 @@ int ha_commit_trans(THD *thd, bool all) // Here, the call will not commit inside InnoDB. It is only working // around closing thd->transaction.stmt open by TR_table::open(). if (all) - commit_one_phase_2(thd, false, &thd->transaction->stmt, false); + commit_one_phase_2(thd, false, &thd->transaction->stmt, false, false); } } #endif @@ -1713,7 +1683,7 @@ int ha_commit_trans(THD *thd, bool all) goto wsrep_err; } #endif /* WITH_WSREP */ - error= ha_commit_one_phase(thd, all); + error= ha_commit_one_phase(thd, all, rw_trans); #ifdef WITH_WSREP // Here in case of error we must return 2 for inconsistency if (run_wsrep_hooks && !error) @@ -1750,7 +1720,7 @@ int ha_commit_trans(THD *thd, bool all) if (!is_real_trans) { - error= commit_one_phase_2(thd, all, trans, is_real_trans); + error= commit_one_phase_2(thd, all, trans, is_real_trans, rw_trans); goto done; } @@ -1784,7 +1754,7 @@ int ha_commit_trans(THD *thd, bool all) DEBUG_SYNC(thd, "ha_commit_trans_after_log_and_order"); DBUG_EXECUTE_IF("crash_commit_after_log", DBUG_SUICIDE();); - error= commit_one_phase_2(thd, all, trans, is_real_trans) ? 2 : 0; + error= commit_one_phase_2(thd, all, trans, is_real_trans, rw_trans) ? 2 : 0; #ifdef WITH_WSREP if (run_wsrep_hooks && (error || (error = wsrep_after_commit(thd, all)))) @@ -1854,16 +1824,6 @@ err: thd->rgi_slave->is_parallel_exec); } end: - if (mdl_request.ticket) - { - /* - We do not always immediately release transactional locks - after ha_commit_trans() (see uses of ha_enable_transaction()), - thus we release the commit blocker lock as soon as it's - not needed. - */ - thd->mdl_context.release_lock(mdl_request.ticket); - } #ifdef WITH_WSREP if (wsrep_is_active(thd) && is_real_trans && !error && (rw_ha_count == 0 || all) && @@ -1879,6 +1839,7 @@ end: /** @note This function does not care about global read lock. A caller should. + However backup locks are handled in commit_one_phase_2. @param[in] all Is set in case of explicit commit (COMMIT statement), or implicit commit @@ -1887,7 +1848,7 @@ end: autocommit=1. */ -int ha_commit_one_phase(THD *thd, bool all) +int ha_commit_one_phase(THD *thd, bool all, bool rw_trans) { THD_TRANS *trans=all ? &thd->transaction->all : &thd->transaction->stmt; /* @@ -1913,20 +1874,48 @@ int ha_commit_one_phase(THD *thd, bool all) if ((res= thd->wait_for_prior_commit())) DBUG_RETURN(res); } - res= commit_one_phase_2(thd, all, trans, is_real_trans); + res= commit_one_phase_2(thd, all, trans, is_real_trans, rw_trans); DBUG_RETURN(res); } static int -commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, bool is_real_trans) +commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, bool is_real_trans, + bool rw_trans) { int error= 0; uint count= 0; Ha_trx_info *ha_info= trans->ha_list, *ha_info_next; + MDL_request mdl_request; + mdl_request.ticket= 0; DBUG_ENTER("commit_one_phase_2"); if (is_real_trans) DEBUG_SYNC(thd, "commit_one_phase_2"); + + if (rw_trans) + { + /* + Acquire a metadata lock which will ensure that COMMIT is blocked + by an active FLUSH TABLES WITH READ LOCK (and vice versa: + COMMIT in progress blocks FTWRL). + + We allow the owner of FTWRL to COMMIT; we assume that it knows + what it does. + */ + MDL_REQUEST_INIT(&mdl_request, MDL_key::BACKUP, "", "", MDL_BACKUP_COMMIT, + MDL_EXPLICIT); + + if (!WSREP(thd) && + thd->mdl_context.acquire_lock(&mdl_request, + thd->variables.lock_wait_timeout)) + { + my_error(ER_ERROR_DURING_COMMIT, MYF(0), 1); + ha_rollback_trans(thd, all); + DBUG_RETURN(1); + } + DEBUG_SYNC(thd, "ha_commit_trans_after_acquire_commit_lock"); + } + if (ha_info) { for (; ha_info; ha_info= ha_info_next) @@ -1955,6 +1944,17 @@ commit_one_phase_2(THD *thd, bool all, THD_TRANS *trans, bool is_real_trans) #endif } } + if (mdl_request.ticket) + { + /* + We do not always immediately release transactional locks + after ha_commit_trans() (see uses of ha_enable_transaction()), + thus we release the commit blocker lock as soon as it's + not needed. + */ + thd->mdl_context.release_lock(mdl_request.ticket); + } + /* Free resources and perform other cleanup even for 'empty' transactions. */ if (is_real_trans) { diff --git a/sql/handler.h b/sql/handler.h index e8315b6ad9a..1acf816dcb9 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -5176,7 +5176,7 @@ int ha_change_key_cache(KEY_CACHE *old_key_cache, KEY_CACHE *new_key_cache); /* transactions: interface to handlerton functions */ int ha_start_consistent_snapshot(THD *thd); int ha_commit_or_rollback_by_xid(XID *xid, bool commit); -int ha_commit_one_phase(THD *thd, bool all); +int ha_commit_one_phase(THD *thd, bool all, bool rw_trans); int ha_commit_trans(THD *thd, bool all); int ha_rollback_trans(THD *thd, bool all); int ha_prepare(THD *thd); diff --git a/sql/mdl.h b/sql/mdl.h index dd10b3a45d0..f6b7154fba0 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -124,6 +124,8 @@ public: */ enum enum_mdl_type { + /* This means that the MDL_request is not initialized */ + MDL_NOT_INITIALIZED= -1, /* An intention exclusive metadata lock (IX). Used only for scoped locks. Owner of this type of lock can acquire upgradable exclusive locks on @@ -599,12 +601,13 @@ public: */ MDL_request& operator=(const MDL_request &) { + type= MDL_NOT_INITIALIZED; ticket= NULL; /* Do nothing, in particular, don't try to copy the key. */ return *this; } /* Another piece of ugliness for TABLE_LIST constructor */ - MDL_request() {} + MDL_request(): type(MDL_NOT_INITIALIZED), ticket(NULL) {} MDL_request(const MDL_request *rhs) :type(rhs->type), diff --git a/sql/rpl_parallel.cc b/sql/rpl_parallel.cc index 94882230682..c12573f817f 100644 --- a/sql/rpl_parallel.cc +++ b/sql/rpl_parallel.cc @@ -1053,10 +1053,10 @@ handle_rpl_parallel_thread(void *arg) server_threads.insert(thd); set_current_thd(thd); pthread_detach_this_thread(); + thd->store_globals(); thd->init_for_queries(); thd->variables.binlog_annotate_row_events= 0; init_thr_lock(); - thd->store_globals(); thd->system_thread= SYSTEM_THREAD_SLAVE_SQL; thd->security_ctx->skip_grants(); thd->variables.max_allowed_packet= slave_max_allowed_packet; diff --git a/sql/sql_insert.cc b/sql/sql_insert.cc index 31badbe2aba..d25410292ef 100644 --- a/sql/sql_insert.cc +++ b/sql/sql_insert.cc @@ -1717,6 +1717,10 @@ int vers_insert_history_row(TABLE *table) if (row_start->cmp(row_start->ptr, row_end->ptr) >= 0) return 0; + if (table->vfield && + table->update_virtual_fields(table->file, VCOL_UPDATE_FOR_READ)) + return HA_ERR_GENERIC; + return table->file->ha_write_row(table->record[0]); } diff --git a/sql/sql_sequence.cc b/sql/sql_sequence.cc index c0925da59e1..b9d500d463c 100644 --- a/sql/sql_sequence.cc +++ b/sql/sql_sequence.cc @@ -1,5 +1,6 @@ /* Copyright (c) 2017, MariaDB Corporation, Alibaba Corporation + Copyrgiht (c) 2020, MariaDB Corporation. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -483,6 +484,10 @@ int SEQUENCE::read_initial_values(TABLE *table) if (mdl_lock_used) thd->mdl_context.release_lock(mdl_request.ticket); write_unlock(table); + + if (!has_active_transaction && !thd->transaction->stmt.is_empty() && + !thd->in_sub_stmt) + trans_commit_stmt(thd); DBUG_RETURN(HA_ERR_LOCK_WAIT_TIMEOUT); } DBUG_ASSERT(table->reginfo.lock_type == TL_READ); diff --git a/sql/table.cc b/sql/table.cc index 32d0ee6f538..d7ea9d447fa 100644 --- a/sql/table.cc +++ b/sql/table.cc @@ -8809,29 +8809,24 @@ void TABLE::vers_update_fields() bitmap_set_bit(write_set, vers_start_field()->field_index); bitmap_set_bit(write_set, vers_end_field()->field_index); - if (versioned(VERS_TIMESTAMP)) + if (!vers_write) { - if (!vers_write) - { - file->column_bitmaps_signal(); - return; - } - if (vers_start_field()->store_timestamp(in_use->query_start(), - in_use->query_start_sec_part())) - DBUG_ASSERT(0); + file->column_bitmaps_signal(); + return; } - else + + if (versioned(VERS_TIMESTAMP) && + vers_start_field()->store_timestamp(in_use->query_start(), + in_use->query_start_sec_part())) { - if (!vers_write) - { - file->column_bitmaps_signal(); - return; - } + DBUG_ASSERT(0); } vers_end_field()->set_max(); bitmap_set_bit(read_set, vers_end_field()->field_index); file->column_bitmaps_signal(); + if (vfield) + update_virtual_fields(file, VCOL_UPDATE_FOR_READ); } diff --git a/sql/xa.cc b/sql/xa.cc index 68e6e67fa0b..7d6d7187bce 100644 --- a/sql/xa.cc +++ b/sql/xa.cc @@ -689,7 +689,8 @@ bool trans_xa_commit(THD *thd) { DEBUG_SYNC(thd, "trans_xa_commit_after_acquire_commit_lock"); - if ((res= MY_TEST(ha_commit_one_phase(thd, 1)))) + res= MY_TEST(ha_commit_one_phase(thd, 1, 1)); + if (res) my_error(ER_XAER_RMERR, MYF(0)); else { diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index b03900d76fa..04645ba025b 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -21150,39 +21150,49 @@ ha_innobase::can_convert_varstring(const Field_varstring* field, return true; } -bool -ha_innobase::can_convert_blob(const Field_blob* field, - const Column_definition& new_type) const +static bool is_part_of_a_key(const Field_blob *field) { - if (new_type.type_handler() != field->type_handler()) { - return false; - } + const TABLE_SHARE *s= field->table->s; - if (!new_type.compression_method() != !field->compression_method()) { - return false; - } + for (uint i= 0; i < s->keys; i++) + { + const KEY &key= s->key_info[i]; + for (uint j= 0; j < key.user_defined_key_parts; j++) + { + const KEY_PART_INFO &info= key.key_part[j]; + if (info.field->field_index == field->field_index) + return true; + } + } - if (new_type.pack_length != field->pack_length()) { - return false; - } + return false; +} - if (new_type.charset != field->charset()) { - Charset field_cs(field->charset()); - if (!field_cs.encoding_allows_reinterpret_as( - new_type.charset)) { - return false; - } +bool ha_innobase::can_convert_blob(const Field_blob *field, + const Column_definition &new_type) const +{ + if (new_type.type_handler() != field->type_handler()) + return false; - if (!field_cs.eq_collation_specific_names(new_type.charset)) { - bool is_part_of_a_key - = !field->part_of_key.is_clear_all(); - return !is_part_of_a_key; - } + if (!new_type.compression_method() != !field->compression_method()) + return false; - return true; - } + if (new_type.pack_length != field->pack_length()) + return false; - return true; + if (new_type.charset != field->charset()) + { + Charset field_cs(field->charset()); + if (!field_cs.encoding_allows_reinterpret_as(new_type.charset)) + return false; + + if (!field_cs.eq_collation_specific_names(new_type.charset)) + return !is_part_of_a_key(field); + + return true; + } + + return true; } Compare_keys ha_innobase::compare_key_parts( @@ -21808,3 +21818,70 @@ ib_push_frm_error( break; } } + +/** Writes 8 bytes to nth tuple field +@param[in] tuple where to write +@param[in] nth index in tuple +@param[in] data what to write +@param[in] buf field data buffer */ +static void set_tuple_col_8(dtuple_t *tuple, int col, uint64_t data, byte *buf) +{ + dfield_t *dfield= dtuple_get_nth_field(tuple, col); + ut_ad(dfield->type.len == 8); + if (dfield->len == UNIV_SQL_NULL) + { + dfield_set_data(dfield, buf, 8); + } + ut_ad(dfield->len == dfield->type.len && dfield->data); + mach_write_to_8(dfield->data, data); +} + +void ins_node_t::vers_update_end(row_prebuilt_t *prebuilt, bool history_row) +{ + ut_ad(prebuilt->ins_node == this); + trx_t *trx= prebuilt->trx; +#ifndef DBUG_OFF + ut_ad(table->vers_start != table->vers_end); + const mysql_row_templ_t *t= prebuilt->get_template_by_col(table->vers_end); + ut_ad(t); + ut_ad(t->mysql_col_len == 8); +#endif + + if (history_row) + { + set_tuple_col_8(row, table->vers_end, trx->id, vers_end_buf); + } + else /* ROW_INS_VERSIONED */ + { + set_tuple_col_8(row, table->vers_end, TRX_ID_MAX, vers_end_buf); +#ifndef DBUG_OFF + t= prebuilt->get_template_by_col(table->vers_start); + ut_ad(t); + ut_ad(t->mysql_col_len == 8); +#endif + set_tuple_col_8(row, table->vers_start, trx->id, vers_start_buf); + } + dict_index_t *clust_index= dict_table_get_first_index(table); + THD *thd= trx->mysql_thd; + TABLE *mysql_table= prebuilt->m_mysql_table; + mem_heap_t *local_heap= NULL; + for (ulint col_no= 0; col_no < dict_table_get_n_v_cols(table); col_no++) + { + + const dict_v_col_t *v_col= dict_table_get_nth_v_col(table, col_no); + for (ulint i= 0; i < unsigned(v_col->num_base); i++) + { + dict_col_t *base_col= v_col->base_col[i]; + if (base_col->ind == table->vers_end) + { + innobase_get_computed_value(row, v_col, clust_index, &local_heap, + table->heap, NULL, thd, mysql_table, + mysql_table->record[0], NULL, NULL, NULL); + } + } + } + if (local_heap) + { + mem_heap_free(local_heap); + } +} diff --git a/storage/innobase/include/row0ins.h b/storage/innobase/include/row0ins.h index 95f4388902d..34427dc6dc7 100644 --- a/storage/innobase/include/row0ins.h +++ b/storage/innobase/include/row0ins.h @@ -163,6 +163,8 @@ row_ins_step( #define INS_NODE_INSERT_ENTRIES 3 /* index entries should be built and inserted */ +struct row_prebuilt_t; + /** Insert node structure */ struct ins_node_t { @@ -203,6 +205,7 @@ struct ins_node_t entry_list and sys fields are stored here; if this is NULL, entry list should be created and buffers for sys fields in row allocated */ + void vers_update_end(row_prebuilt_t *prebuilt, bool history_row); }; /** Create an insert object. diff --git a/storage/innobase/include/row0upd.h b/storage/innobase/include/row0upd.h index 65f70be7134..f9d949fc2d7 100644 --- a/storage/innobase/include/row0upd.h +++ b/storage/innobase/include/row0upd.h @@ -354,7 +354,32 @@ struct upd_t{ fields[n_fields++] = field; } - /** Determine if the given field_no is modified. + void remove_element(ulint i) + { + ut_ad(n_fields > 0); + ut_ad(i < n_fields); + while (i < n_fields - 1) + { + fields[i]= fields[i + 1]; + i++; + } + n_fields--; + } + + bool remove(const ulint field_no) + { + for (ulint i= 0; i < n_fields; ++i) + { + if (field_no == fields[i].field_no) + { + remove_element(i); + return true; + } + } + return false; + } + + /** Determine if the given field_no is modified. @return true if modified, false otherwise. */ bool is_modified(uint16_t field_no) const { @@ -494,25 +519,25 @@ private: make_versioned_delete(). @param[in] trx transaction @param[in] vers_sys_idx table->row_start or table->row_end */ - void make_versioned_helper(const trx_t* trx, ulint idx); + void vers_update_fields(const trx_t *trx, ulint idx); public: /** Also set row_start = CURRENT_TIMESTAMP/trx->id @param[in] trx transaction */ - void make_versioned_update(const trx_t* trx) - { - make_versioned_helper(trx, table->vers_start); - } + void vers_make_update(const trx_t *trx) + { + vers_update_fields(trx, table->vers_start); + } /** Only set row_end = CURRENT_TIMESTAMP/trx->id. Do not touch other fields at all. @param[in] trx transaction */ - void make_versioned_delete(const trx_t* trx) - { + void vers_make_delete(const trx_t *trx) + { update->n_fields = 0; is_delete = VERSIONED_DELETE; - make_versioned_helper(trx, table->vers_end); - } + vers_update_fields(trx, table->vers_end); + } }; #define UPD_NODE_MAGIC_N 1579975 diff --git a/storage/innobase/row/row0mysql.cc b/storage/innobase/row/row0mysql.cc index 20f06fc5444..296c273a779 100644 --- a/storage/innobase/row/row0mysql.cc +++ b/storage/innobase/row/row0mysql.cc @@ -1331,23 +1331,6 @@ row_mysql_get_table_status( return(err); } -/** Writes 8 bytes to nth tuple field -@param[in] tuple where to write -@param[in] nth index in tuple -@param[in] data what to write -@param[in] buf field data buffer */ -static -void -set_tuple_col_8(dtuple_t* tuple, int col, uint64_t data, byte* buf) { - dfield_t* dfield = dtuple_get_nth_field(tuple, col); - ut_ad(dfield->type.len == 8); - if (dfield->len == UNIV_SQL_NULL) { - dfield_set_data(dfield, buf, 8); - } - ut_ad(dfield->len == dfield->type.len && dfield->data); - mach_write_to_8(dfield->data, data); -} - /** Does an insert for MySQL. @param[in] mysql_rec row in the MySQL format @param[in,out] prebuilt prebuilt struct in MySQL handle @@ -1415,29 +1398,8 @@ row_insert_for_mysql( &blob_heap); if (ins_mode != ROW_INS_NORMAL) { -#ifndef DBUG_OFF - ut_ad(table->vers_start != table->vers_end); - const mysql_row_templ_t* t - = prebuilt->get_template_by_col(table->vers_end); - ut_ad(t); - ut_ad(t->mysql_col_len == 8); -#endif - - if (ins_mode == ROW_INS_HISTORICAL) { - set_tuple_col_8(node->row, table->vers_end, trx->id, - node->vers_end_buf); - } else /* ROW_INS_VERSIONED */ { - set_tuple_col_8(node->row, table->vers_end, TRX_ID_MAX, - node->vers_end_buf); -#ifndef DBUG_OFF - t = prebuilt->get_template_by_col(table->vers_start); - ut_ad(t); - ut_ad(t->mysql_col_len == 8); -#endif - set_tuple_col_8(node->row, table->vers_start, trx->id, - node->vers_start_buf); - } - } + node->vers_update_end(prebuilt, ins_mode == ROW_INS_HISTORICAL); + } savept = trx_savept_take(trx); @@ -1871,10 +1833,10 @@ row_update_for_mysql(row_prebuilt_t* prebuilt) if (prebuilt->versioned_write) { if (node->is_delete == VERSIONED_DELETE) { - node->make_versioned_delete(trx); - } else if (node->update->affects_versioned()) { - node->make_versioned_update(trx); - } + node->vers_make_delete(trx); + } else if (node->update->affects_versioned()) { + node->vers_make_update(trx); + } } for (;;) { @@ -2230,14 +2192,14 @@ row_update_cascade_for_mysql( if (table->versioned()) { if (node->is_delete == PLAIN_DELETE) { - node->make_versioned_delete(trx); - } else if (node->update->affects_versioned()) { + node->vers_make_delete(trx); + } else if (node->update->affects_versioned()) { dberr_t err = row_update_vers_insert(thr, node); if (err != DB_SUCCESS) { return err; } - node->make_versioned_update(trx); - } + node->vers_make_update(trx); + } } for (;;) { diff --git a/storage/innobase/row/row0upd.cc b/storage/innobase/row/row0upd.cc index fe81cb46916..2639f3ec7ea 100644 --- a/storage/innobase/row/row0upd.cc +++ b/storage/innobase/row/row0upd.cc @@ -3173,34 +3173,59 @@ Supposed to be called only by make_versioned_update() and make_versioned_delete(). @param[in] trx transaction @param[in] vers_sys_idx table->row_start or table->row_end */ -void upd_node_t::make_versioned_helper(const trx_t* trx, ulint idx) +void upd_node_t::vers_update_fields(const trx_t *trx, ulint idx) { - ut_ad(in_mysql_interface); // otherwise needs to recalculate - // node->cmpl_info - ut_ad(idx == table->vers_start || idx == table->vers_end); - - dict_index_t* clust_index = dict_table_get_first_index(table); - - /* row_create_update_node_for_mysql() pre-allocated this much. - At least one PK column always remains unchanged. */ - ut_ad(update->n_fields < ulint(table->n_cols + table->n_v_cols)); - - update->n_fields++; - upd_field_t* ufield = upd_get_nth_field(update, update->n_fields - 1); - const dict_col_t* col = dict_table_get_nth_col(table, idx); - - upd_field_set_field_no(ufield, static_cast<uint16_t>( - dict_col_get_clust_pos( - col, clust_index)), - clust_index); - - char* where = reinterpret_cast<char*>(update->vers_sys_value); - if (col->vers_native()) { - mach_write_to_8(where, trx->id); - } else { - thd_get_query_start_data(trx->mysql_thd, where); - } - - dfield_set_data(&ufield->new_val, update->vers_sys_value, col->len); + ut_ad(in_mysql_interface); // otherwise needs to recalculate node->cmpl_info + ut_ad(idx == table->vers_start || idx == table->vers_end); + + dict_index_t *clust_index= dict_table_get_first_index(table); + const dict_col_t *col= dict_table_get_nth_col(table, idx); + ulint field_no= dict_col_get_clust_pos(col, clust_index); + upd_field_t *ufield; + + for (ulint i= 0; i < update->n_fields; ++i) + { + if (update->fields[i].field_no == field_no) + { + ufield= &update->fields[i]; + goto skip_append; + } + } + + /* row_create_update_node_for_mysql() pre-allocated this much. + At least one PK column always remains unchanged. */ + ut_ad(update->n_fields < ulint(table->n_cols + table->n_v_cols)); + + update->n_fields++; + ufield= upd_get_nth_field(update, update->n_fields - 1); + upd_field_set_field_no(ufield, static_cast<uint16_t>(field_no), clust_index); + +skip_append: + char *where= reinterpret_cast<char *>(update->vers_sys_value); + if (col->vers_native()) + mach_write_to_8(where, trx->id); + else + thd_get_query_start_data(trx->mysql_thd, where); + + dfield_set_data(&ufield->new_val, update->vers_sys_value, col->len); + + for (ulint col_no= 0; col_no < dict_table_get_n_v_cols(table); col_no++) + { + const dict_v_col_t *v_col= dict_table_get_nth_v_col(table, col_no); + if (!v_col->m_col.ord_part) + continue; + for (ulint i= 0; i < unsigned(v_col->num_base); i++) + { + dict_col_t *base_col= v_col->base_col[i]; + if (base_col->ind == col->ind) + { + /* Virtual column depends on system field value + which we updated above. Remove it from update + vector, so it is recalculated in + row_upd_store_v_row() (see !update branch). */ + update->remove(v_col->v_pos); + break; + } + } + } } - diff --git a/storage/innobase/trx/trx0trx.cc b/storage/innobase/trx/trx0trx.cc index 03e60d80d5b..fb594b0b099 100644 --- a/storage/innobase/trx/trx0trx.cc +++ b/storage/innobase/trx/trx0trx.cc @@ -1272,7 +1272,8 @@ trx_update_mod_tables_timestamp( dict_table_t* table = it->first; table->update_time = now; #ifdef UNIV_DEBUG - if (preserve_tables || table->get_ref_count()) { + if (preserve_tables || table->get_ref_count() + || UT_LIST_GET_LEN(table->locks)) { /* do not evict when committing DDL operations or if some other transaction is holding the table handle */ @@ -1281,7 +1282,11 @@ trx_update_mod_tables_timestamp( /* recheck while holding the mutex that blocks table->acquire() */ mutex_enter(&dict_sys.mutex); - if (!table->get_ref_count()) { + mutex_enter(&lock_sys.mutex); + const bool do_evict = !table->get_ref_count() + && !UT_LIST_GET_LEN(table->locks); + mutex_exit(&lock_sys.mutex); + if (do_evict) { dict_sys.remove(table, true); } mutex_exit(&dict_sys.mutex); diff --git a/storage/maria/ha_maria.cc b/storage/maria/ha_maria.cc index c257a2094f1..3c43595533f 100644 --- a/storage/maria/ha_maria.cc +++ b/storage/maria/ha_maria.cc @@ -2947,6 +2947,10 @@ static void reset_thd_trn(THD *thd, MARIA_HA *first_table) DBUG_VOID_RETURN; } +bool ha_maria::has_active_transaction(THD *thd) +{ + return (maria_hton && THD_TRN); +} /** Performs an implicit commit of the Maria transaction and creates a new diff --git a/storage/maria/ha_maria.h b/storage/maria/ha_maria.h index bff7ace8813..ddf8fc6f229 100644 --- a/storage/maria/ha_maria.h +++ b/storage/maria/ha_maria.h @@ -158,6 +158,7 @@ public: { return file; } + static bool has_active_transaction(THD *thd); static int implicit_commit(THD *thd, bool new_trn); /** * Multi Range Read interface |