mirror of
https://github.com/MariaDB/server.git
synced 2025-12-18 10:15:43 +01:00
Atomic CREATE OR REPLACE allows to keep an old table intact if the
command fails or during the crash. That is done by renaming the
original table to temporary name, as a backup and restoring it if the
CREATE fails. When the command is complete and logged the backup
table is deleted.
Atomic replace algorithm
Two DDL chains are used for CREATE OR REPLACE:
ddl_log_state_create (C) and ddl_log_state_rm (D).
1. (C) Log rename of ORIG to TMP table (Rename TMP to original).
2. Rename orignal to TMP.
3. (C) Log CREATE_TABLE_ACTION of ORIG (drops ORIG);
4. Do everything with ORIG (like insert data)
5. (D) Log drop of TMP
6. Write query to binlog (this marks (C) to be closed in
case of failure)
7. Execute drop of TMP through (D)
8. Close (C) and (D)
If there is a failure before 6) we revert the changes in (C)
Chain (D) is only executed if 6) succeded (C is closed on
crash recovery).
Foreign key errors will be found at the 1) stage.
Additional notes
- CREATE TABLE without REPLACE and temporary tables is not affected
by this commit.
set @@drop_before_create_or_replace=1 can be used to
get old behaviour where existing tables are dropped
in CREATE OR REPLACE.
- CREATE TABLE is reverted if binlogging the query fails.
- Engines having HTON_EXPENSIVE_RENAME flag set are not affected by
this commit. Conflicting tables marked with this flag will be
deleted with CREATE OR REPLACE.
- Replication execution is not affected by this commit.
- Replication will first drop the conflicting table and then
creating the new one.
- CREATE TABLE .. SELECT XID usage is fixed and now there is no need
to log DROP TABLE via DDL_CREATE_TABLE_PHASE_LOG (see comments in
do_postlock()). XID is now correctly updated so it disables
DDL_LOG_DROP_TABLE_ACTION. Note that binary log is flushed at the
final stage when the table is ready. So if we have XID in the
binary log we don't need to drop the table.
- Three variations of CREATE OR REPLACE handled:
1. CREATE OR REPLACE TABLE t1 (..);
2. CREATE OR REPLACE TABLE t1 LIKE t2;
3. CREATE OR REPLACE TABLE t1 SELECT ..;
- Test case uses 6 combinations for engines (aria, aria_notrans,
myisam, ib, lock_tables, expensive_rename) and 2 combinations for
binlog types (row, stmt). Combinations help to check differences
between the results. Error failures are tested for the above three
variations.
- expensive_rename tests CREATE OR REPLACE without atomic
replace. The effect should be the same as with the old behaviour
before this commit.
- Triggers mechanism is unaffected by this change. This is tested in
create_replace.test.
- LOCK TABLES is affected. Lock restoration must be done after new
table is created or TMP is renamed back to ORIG
- Moved ddl_log_complete() from send_eof() to finalize_ddl(). This
checkpoint was not executed before for normal CREATE TABLE but is
executed now.
- CREATE TABLE will now rollback also if writing to the binary
logging failed. See rpl_gtid_strict.test
backup ddl log changes
- In case of a successfull CREATE OR REPLACE we only log
the CREATE event, not the DROP TABLE event of the old table.
ddl_log.cc changes
ddl_log_execute_action() now properly return error conditions.
ddl_log_disable_entry() added to allow one to disable one entry.
The entry on disk is still reserved until ddl_log_complete() is
executed.
On XID usage
Like with all other atomic DDL operations XID is used to avoid
inconsistency between master and slave in the case of a crash after
binary log is written and before ddl_log_state_create is closed. On
recovery XIDs are taken from binary log and corresponding DDL log
events get disabled. That is done by
ddl_log_close_binlogged_events().
On linking two chains together
Chains are executed in the ascending order of entry_pos of execute
entries. But entry_pos assignment order is undefined: it may assign
bigger number for the first chain and then smaller number for the
second chain. So the execution order in that case will be reverse:
second chain will be executed first.
To avoid that we link one chain to another. While the base chain
(ddl_log_state_create) is active the secondary chain
(ddl_log_state_rm) is not executed. That is: only one chain can be
executed in two linked chains.
The interface ddl_log_link_chains() was defined in "MDEV-22166
ddl_log_write_execute_entry() extension".
Atomic info parameters in HA_CREATE_INFO
Many functions in CREATE TABLE pass the same parameters. These
parameters are part of table creation info and should be in
HA_CREATE_INFO (or whatever). Passing parameters via single
structure is much easier for adding new data and
refactoring.
InnoDB changes
Added ha_innobase::can_be_renamed_to_backup() to check if
a table with foreign keys can be renamed.
Aria changes:
- Fixed issue in Aria engine with CREATE + locked tables
that data was not properly commited in some cases in
case of crashes.
Known issues:
- InnoDB tables with foreign key definitions are not fully supported
with atomic create and replace:
- ha_innobase::can_be_renamed_to_backup() can detect some cases
where InnoDB does not support renaming table with foreign key
constraints. In this case MariaDB will drop the old table before
creating the new one.
The detected cases are:
- The new and old table is using the same foreign key constraint
name.
- The old table has self referencing constraints.
- If the old and new table uses the same name for a constraint the
create of the new table will fail. The orignal table will be
restored in this case.
- The above issues will be fixed in a future commit.
- CREATE OR REPLACE TEMPORARY table is not full atomic. Any conflicting
table will always be dropped before creating a new one. (Old behaviour).
257 lines
6.8 KiB
PHP
257 lines
6.8 KiB
PHP
# Test CREATE OR REPLACE TABLE in replication
|
|
--source include/have_innodb.inc
|
|
|
|
--let $rpl_topology=1->2
|
|
--source include/rpl_init.inc
|
|
|
|
select @@binlog_format, @@create_temporary_table_binlog_formats;
|
|
|
|
# Copy create_temporary_table_binlog_formats from master to slave
|
|
# This is done to get same results as with older versions of MariaDB.
|
|
# The slave will work even if this is not done. However in mixed
|
|
# format on slave temporary tables would not be logged to in the
|
|
# slaves binary log
|
|
let $format=`select @@create_temporary_table_binlog_formats`;
|
|
connection server_2;
|
|
--eval set @@global.create_temporary_table_binlog_formats='$format'
|
|
# Ensure that the slave threads uses the new values.
|
|
--source include/stop_slave.inc
|
|
--source include/start_slave.inc
|
|
connection server_1;
|
|
|
|
--echo #
|
|
--echo # Create help tables
|
|
--echo #
|
|
create table t2 (a int) engine=myisam;
|
|
insert into t2 values (0),(1),(2),(2);
|
|
create temporary table t3 (a_in_temporary int) engine=myisam;
|
|
|
|
--echo #
|
|
--echo # Check how create table and create or replace table are logged
|
|
--echo #
|
|
|
|
save_master_pos;
|
|
connection server_2;
|
|
sync_with_master;
|
|
create table t1 (to_be_deleted int);
|
|
|
|
connection server_1;
|
|
CREATE TABLE t1 AS SELECT 1 AS f1;
|
|
CREATE OR REPLACE TABLE t1 AS SELECT 2 AS f1;
|
|
CREATE OR REPLACE table t1 like t2;
|
|
CREATE OR REPLACE table t1 like t3;
|
|
drop table t1;
|
|
|
|
--echo binlog from server 1
|
|
--source include/show_binlog_events.inc
|
|
save_master_pos;
|
|
connection server_2;
|
|
sync_with_master;
|
|
--echo binlog from server 2
|
|
--source include/show_binlog_events.inc
|
|
|
|
connection server_1;
|
|
|
|
--echo #
|
|
--echo # Ensure that also failed create_or_replace are logged
|
|
--echo #
|
|
|
|
--let $binlog_start=query_get_value(SHOW MASTER STATUS, Position, 1)
|
|
|
|
create table t1 (a int);
|
|
--error ER_TABLE_MUST_HAVE_COLUMNS
|
|
create or replace table t1;
|
|
drop table if exists t1;
|
|
# The following is not logged as t1 does not exists;
|
|
--error ER_DUP_ENTRY
|
|
create or replace table t1 (a int primary key) select a from t2;
|
|
|
|
create table t1 (a int);
|
|
# This should as a delete as we will delete t1
|
|
--error ER_DUP_ENTRY
|
|
create or replace table t1 (a int primary key) select a from t2;
|
|
|
|
# Same with temporary table
|
|
create temporary table t9 (a int);
|
|
|
|
--error ER_DUP_ENTRY
|
|
create or replace temporary table t9 (a int primary key) select a from t2;
|
|
|
|
drop table t1;
|
|
|
|
--echo binlog from server 1
|
|
--source include/show_binlog_events.inc
|
|
save_master_pos;
|
|
connection server_2;
|
|
sync_with_master;
|
|
show tables;
|
|
connection server_1;
|
|
|
|
--let $binlog_start=query_get_value(SHOW MASTER STATUS, Position, 1)
|
|
create table t1 (a int);
|
|
--error ER_DUP_FIELDNAME
|
|
create or replace table t1 (a int, a int) select * from t2;
|
|
--source include/show_binlog_events.inc
|
|
|
|
drop table if exists t1,t2;
|
|
drop temporary table if exists t9;
|
|
|
|
--echo #
|
|
--echo # Ensure that CREATE are run as CREATE OR REPLACE on slave
|
|
--echo #
|
|
|
|
save_master_pos;
|
|
connection server_2;
|
|
sync_with_master;
|
|
create table t1 (server_2_to_be_delete int);
|
|
connection server_1;
|
|
create table t1 (new_table int);
|
|
|
|
save_master_pos;
|
|
connection server_2;
|
|
sync_with_master;
|
|
|
|
show create table t1;
|
|
connection server_1;
|
|
drop table t1;
|
|
|
|
--echo #
|
|
--echo # Check how CREATE is logged on slave in case of conflicts
|
|
--echo #
|
|
|
|
save_master_pos;
|
|
connection server_2;
|
|
sync_with_master;
|
|
--let $binlog_start=query_get_value(SHOW MASTER STATUS, Position, 1)
|
|
create table t1 (server_2_to_be_delete int);
|
|
create table t2 (server_2_to_be_delete int);
|
|
create table t4 (server_2_to_be_delete int);
|
|
set @org_binlog_format=@@binlog_format;
|
|
set @@global.binlog_format="ROW";
|
|
stop slave;
|
|
--source include/wait_for_slave_to_stop.inc
|
|
start slave;
|
|
--source include/wait_for_slave_to_start.inc
|
|
connection server_1;
|
|
create temporary table t9 (a int);
|
|
insert into t9 values(1);
|
|
create table t1 (new_table int);
|
|
create table t2 select * from t9;
|
|
create table t4 like t9;
|
|
create table t5 select * from t9;
|
|
save_master_pos;
|
|
connection server_2;
|
|
sync_with_master;
|
|
--echo binlog from server 2
|
|
--source include/show_binlog_events.inc
|
|
set @@global.binlog_format=@org_binlog_format;
|
|
stop slave;
|
|
--source include/wait_for_slave_to_stop.inc
|
|
start slave;
|
|
--source include/wait_for_slave_to_start.inc
|
|
connection server_1;
|
|
drop table t1,t2,t4,t5,t9;
|
|
|
|
--echo #
|
|
--echo # Ensure that DROP TABLE is run as DROP IF NOT EXISTS
|
|
--echo #
|
|
|
|
create table t1 (server_1_ver_1 int);
|
|
create table t4 (server_1_ver_2 int);
|
|
|
|
save_master_pos;
|
|
connection server_2;
|
|
sync_with_master;
|
|
--let $binlog_start=query_get_value(SHOW MASTER STATUS, Position, 1)
|
|
|
|
# Drop the table on the slave
|
|
drop table t1;
|
|
connection server_1;
|
|
drop table t1,t4;
|
|
create table t1 (server_2_ver_2 int);
|
|
save_master_pos;
|
|
connection server_2;
|
|
sync_with_master;
|
|
show create table t1;
|
|
--echo binlog from server 2
|
|
--source include/show_binlog_events.inc
|
|
connection server_1;
|
|
drop table t1;
|
|
|
|
--echo #
|
|
--echo # Ensure that CREATE ... SELECT is recorded as one GTID on the slave
|
|
--echo #
|
|
|
|
save_master_pos;
|
|
connection server_2;
|
|
sync_with_master;
|
|
--let $binlog_start=query_get_value(SHOW MASTER STATUS, Position, 1)
|
|
connection server_1;
|
|
|
|
create table t1 (a int);
|
|
insert into t1 values (0),(1),(2);
|
|
create table t2 engine=myisam select * from t1;
|
|
create or replace table t2 engine=innodb select * from t1;
|
|
save_master_pos;
|
|
connection server_2;
|
|
sync_with_master;
|
|
--echo binlog from server 2
|
|
--source include/show_binlog_events.inc
|
|
connection server_1;
|
|
drop table t1;
|
|
|
|
--echo #
|
|
--echo # Check logging of drop temporary table
|
|
--echo #
|
|
|
|
drop temporary table t3;
|
|
|
|
--let $binlog_start=query_get_value(SHOW MASTER STATUS, Position, 1)
|
|
|
|
set @org_binlog_format=@@binlog_format;
|
|
set binlog_format="STATEMENT";
|
|
create temporary table t5 (a int);
|
|
drop temporary table t5;
|
|
set binlog_format="ROW";
|
|
create temporary table t6 (a int);
|
|
drop temporary table t6;
|
|
set binlog_format="STATEMENT";
|
|
create temporary table t7 (a int);
|
|
set binlog_format="ROW";
|
|
drop temporary table t7;
|
|
create temporary table t8 (a int);
|
|
--error ER_TEMP_TABLE_PREVENTS_SWITCH_OUT_OF_RBR
|
|
set binlog_format="STATEMENT";
|
|
drop temporary table t8;
|
|
set @@binlog_format=@org_binlog_format;
|
|
|
|
# MDEV-20091:
|
|
# 1. No DROP should be logged for non-existing tmp table, nor
|
|
# 2. at the connection close when its creation has not been logged.
|
|
set @@session.binlog_format=default;
|
|
drop temporary table if exists t9;
|
|
|
|
--connect(con1,localhost,root,,)
|
|
--let $conid = `SELECT CONNECTION_ID()`
|
|
set session binlog_format=default;
|
|
create temporary table t9 (i int);
|
|
--echo *** Must be no DROP logged for t9 when there was no CREATE, at disconnect too ***
|
|
--disconnect con1
|
|
|
|
--connection server_1
|
|
# The disconnect runs asynchroneously. Wait for it to complete, otherwise the
|
|
# DROP TEMPORARY TABLE may not have been binlogged yet when SHOW BINLOG EVENTS
|
|
# is run.
|
|
--let $wait_condition= SELECT COUNT(*)=0 FROM INFORMATION_SCHEMA.PROCESSLIST WHERE ID=$conid
|
|
--source include/wait_condition.inc
|
|
|
|
--source include/show_binlog_events.inc
|
|
|
|
# Clean up
|
|
drop table t2;
|
|
|
|
--connection server_2
|
|
set @@global.create_temporary_table_binlog_formats=default;
|
|
--connection server_1
|
|
|
|
--source include/rpl_end.inc
|