diff --git a/mysql-test/suite/galera/r/MDEV-41012-collision.result b/mysql-test/suite/galera/r/MDEV-41012-collision.result new file mode 100644 index 0000000000000..dc3af178d72c9 --- /dev/null +++ b/mysql-test/suite/galera/r/MDEV-41012-collision.result @@ -0,0 +1,115 @@ +connection node_2; +connection node_1; +# +# Each pair below is two distinct values whose bytes fold to the same +# latin1_swedish_ci weight string (0xC0->0x41, 0x6A->0x4A, 0x6B->0x4B, +# 0xD2->0x4F, 0x7A->0x5A). A UUID is stored as a byte permutation of the +# value shown, which does not affect a byte-wise folding. +# +SELECT WEIGHT_STRING(_latin1 0xC0A8016B9E4D4F7AB3D26A1E8F9C0D1B) = +WEIGHT_STRING(_latin1 0x41A8014B9E4D4F5AB34F4A1E8F9C0D1B) AS uuid_pair_collides, +WEIGHT_STRING(_latin1 0x20010DB80000000000000000C0A8016B) = +WEIGHT_STRING(_latin1 0x20010DB8000000000000000041A8014B) AS inet6_pair_collides, +WEIGHT_STRING(_latin1 0xC0A8016B) = +WEIGHT_STRING(_latin1 0x41A8014B) AS inet4_pair_collides; +uuid_pair_collides inet6_pair_collides inet4_pair_collides +1 1 1 +# +# UUID +# +connection node_1; +CREATE TABLE t (id UUID PRIMARY KEY, v INT) ENGINE=InnoDB; +INSERT INTO t VALUES ('c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b', 0), ('41a8014b-9e4d-4f5a-b34f-4a1e8f9c0d1b', 0); +connection node_2; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_slave_threads = 2; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_1; +UPDATE t SET v = 1 WHERE id = 'c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b'; +connection node_2; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE t SET v = 2 WHERE id = '41a8014b-9e4d-4f5a-b34f-4a1e8f9c0d1b'; +connection node_2; +no_false_dependency +1 +SELECT COUNT(*) = 1 AS second_row_applied_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; +second_row_applied_in_parallel +1 +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SELECT id, v FROM t ORDER BY v; +id v +c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b 1 +41a8014b-9e4d-4f5a-b34f-4a1e8f9c0d1b 2 +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; +connection node_1; +DROP TABLE t; +# +# INET6 +# +connection node_1; +CREATE TABLE t (id INET6 PRIMARY KEY, v INT) ENGINE=InnoDB; +INSERT INTO t VALUES ('2001:db8::c0a8:16b', 0), ('2001:db8::41a8:14b', 0); +connection node_2; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_slave_threads = 2; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_1; +UPDATE t SET v = 1 WHERE id = '2001:db8::c0a8:16b'; +connection node_2; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE t SET v = 2 WHERE id = '2001:db8::41a8:14b'; +connection node_2; +no_false_dependency +1 +SELECT COUNT(*) = 1 AS second_row_applied_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; +second_row_applied_in_parallel +1 +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SELECT id, v FROM t ORDER BY v; +id v +2001:db8::c0a8:16b 1 +2001:db8::41a8:14b 2 +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; +connection node_1; +DROP TABLE t; +# +# INET4 +# +connection node_1; +CREATE TABLE t (id INET4 PRIMARY KEY, v INT) ENGINE=InnoDB; +INSERT INTO t VALUES ('192.168.1.107', 0), ('65.168.1.75', 0); +connection node_2; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_slave_threads = 2; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_1; +UPDATE t SET v = 1 WHERE id = '192.168.1.107'; +connection node_2; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE t SET v = 2 WHERE id = '65.168.1.75'; +connection node_2; +no_false_dependency +1 +SELECT COUNT(*) = 1 AS second_row_applied_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; +second_row_applied_in_parallel +1 +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SELECT id, v FROM t ORDER BY v; +id v +192.168.1.107 1 +65.168.1.75 2 +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; +connection node_1; +DROP TABLE t; diff --git a/mysql-test/suite/galera/r/MDEV-41012.result b/mysql-test/suite/galera/r/MDEV-41012.result new file mode 100644 index 0000000000000..8f1b258f64a7f --- /dev/null +++ b/mysql-test/suite/galera/r/MDEV-41012.result @@ -0,0 +1,104 @@ +connection node_2; +connection node_1; +# +# UUID +# +connection node_1; +CREATE TABLE p (id UUID PRIMARY KEY, v INT) ENGINE=InnoDB; +CREATE TABLE c (id INT AUTO_INCREMENT PRIMARY KEY, p_id UUID NOT NULL, KEY k (p_id), FOREIGN KEY (p_id) REFERENCES p (id)) ENGINE=InnoDB; +INSERT INTO p VALUES ('c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b', 0); +connection node_2; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_slave_threads = 2; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_1; +INSERT INTO c (p_id) VALUES ('c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b'); +connection node_2; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p SET v = v + 1 WHERE id = 'c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b'; +connection node_2; +SELECT COUNT(*) = 0 AS no_row_modified_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; +no_row_modified_in_parallel +1 +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SELECT v FROM p; +v +1 +SELECT COUNT(*) FROM c; +COUNT(*) +1 +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; +connection node_1; +DROP TABLE c, p; +# +# INET6 +# +connection node_1; +CREATE TABLE p (id INET6 PRIMARY KEY, v INT) ENGINE=InnoDB; +CREATE TABLE c (id INT AUTO_INCREMENT PRIMARY KEY, p_id INET6 NOT NULL, KEY k (p_id), FOREIGN KEY (p_id) REFERENCES p (id)) ENGINE=InnoDB; +INSERT INTO p VALUES ('2001:db8::c0a8:16b', 0); +connection node_2; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_slave_threads = 2; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_1; +INSERT INTO c (p_id) VALUES ('2001:db8::c0a8:16b'); +connection node_2; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p SET v = v + 1 WHERE id = '2001:db8::c0a8:16b'; +connection node_2; +SELECT COUNT(*) = 0 AS no_row_modified_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; +no_row_modified_in_parallel +1 +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SELECT v FROM p; +v +1 +SELECT COUNT(*) FROM c; +COUNT(*) +1 +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; +connection node_1; +DROP TABLE c, p; +# +# INET4 +# +connection node_1; +CREATE TABLE p (id INET4 PRIMARY KEY, v INT) ENGINE=InnoDB; +CREATE TABLE c (id INT AUTO_INCREMENT PRIMARY KEY, p_id INET4 NOT NULL, KEY k (p_id), FOREIGN KEY (p_id) REFERENCES p (id)) ENGINE=InnoDB; +INSERT INTO p VALUES ('192.168.1.107', 0); +connection node_2; +SET SESSION wsrep_sync_wait = 0; +SET GLOBAL wsrep_slave_threads = 2; +SET GLOBAL wsrep_provider_options = 'dbug=d,apply_monitor_slave_enter_sync'; +connection node_1; +INSERT INTO c (p_id) VALUES ('192.168.1.107'); +connection node_2; +SET SESSION wsrep_on = 0; +SET SESSION wsrep_on = 1; +SET GLOBAL wsrep_provider_options = 'dbug='; +connection node_1; +UPDATE p SET v = v + 1 WHERE id = '192.168.1.107'; +connection node_2; +SELECT COUNT(*) = 0 AS no_row_modified_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; +no_row_modified_in_parallel +1 +SET GLOBAL wsrep_provider_options = 'signal=apply_monitor_slave_enter_sync'; +SELECT v FROM p; +v +1 +SELECT COUNT(*) FROM c; +COUNT(*) +1 +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; +connection node_1; +DROP TABLE c, p; diff --git a/mysql-test/suite/galera/t/MDEV-41012-collision.inc b/mysql-test/suite/galera/t/MDEV-41012-collision.inc new file mode 100644 index 0000000000000..4922009de677a --- /dev/null +++ b/mysql-test/suite/galera/t/MDEV-41012-collision.inc @@ -0,0 +1,67 @@ +# +# Body of galera.MDEV-41012-collision, parameterized by the data type. +# +# $fbt_type - data type of the primary key column +# $fbt_value1 - a literal of that type +# $fbt_value2 - a *different* literal whose binary representation folds to +# the same latin1_swedish_ci weight string as $fbt_value1 +# + +--connection node_1 +--eval CREATE TABLE t (id $fbt_type PRIMARY KEY, v INT) ENGINE=InnoDB +--eval INSERT INTO t VALUES ($fbt_value1, 0), ($fbt_value2, 0) + +--connection node_2 +SET SESSION wsrep_sync_wait = 0; +--let $wait_condition = SELECT COUNT(*) = 2 FROM t +--source include/wait_condition.inc + +SET GLOBAL wsrep_slave_threads = 2; + +# Park the applier of the first UPDATE on entry to the apply monitor. +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_1 +--eval UPDATE t SET v = 1 WHERE id = $fbt_value1 + +--connection node_2 +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_wait_sync_point.inc + +# Disarm, so the second applier runs into the real dependency check. +--source include/galera_clear_sync_point.inc + +--let $apply_waits = `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_apply_waits'` + +--connection node_1 +--eval UPDATE t SET v = 2 WHERE id = $fbt_value2 + +--connection node_2 +# The two statements modify two *different* rows, so their +# certification keys must differ, there must be no dependency between the two +# writesets, and the second applier must be free to apply its row while the +# first one is still parked. +--let $wait_condition = SELECT (SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_apply_waits') > $apply_waits OR EXISTS (SELECT 1 FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0) +--source include/wait_condition.inc + +# While the key was collated with latin1_swedish_ci the two keys were equal, +# the second writeset was made to depend on the first, and both of these +# reported 0. +--disable_query_log +--eval SELECT VARIABLE_VALUE = $apply_waits AS no_false_dependency FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_apply_waits' +--enable_query_log +SELECT COUNT(*) = 1 AS second_row_applied_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; + +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--let $wait_condition = SELECT SUM(v) = 3 FROM t +--source include/wait_condition.inc +SELECT id, v FROM t ORDER BY v; + +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; + +--connection node_1 +DROP TABLE t; diff --git a/mysql-test/suite/galera/t/MDEV-41012-collision.test b/mysql-test/suite/galera/t/MDEV-41012-collision.test new file mode 100644 index 0000000000000..f9bfba89e4530 --- /dev/null +++ b/mysql-test/suite/galera/t/MDEV-41012-collision.test @@ -0,0 +1,64 @@ +# +# MDEV-41012: certification key collision between distinct values of a fixed +# binary data type (UUID, INET6, INET4). +# +# Testing key collisions with data types on Field_fbt: UUID, INET6 and INET4, +# where different keys could potentially generate same certification keys. +# i.e. a scenario where two transactions modifying two *different* +# rows certified as a conflict and the later one was aborted with a spurious +# ER_LOCK_DEADLOCK. +# +# A shared key also gives the two writesets a false dependency on each other, +# which serializes applying that should have run in parallel. The test detects +# the collision that way, because it can be driven deterministically. Whether +# the spurious ER_LOCK_DEADLOCK is observed instead depends on whether the +# first writeset has already been applied by the time the second transaction +# replicates, which is a race. +# +# This needs no foreign key: it is the row key path, +# wsrep_store_key_val_for_row(), alone. +# +# This is the second half of MDEV-41012. The other half, a key *mismatch* for +# one and the same row, is covered by galera.MDEV-41012. +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/galera_have_debug_sync.inc + +--echo # +--echo # Each pair below is two distinct values whose bytes fold to the same +--echo # latin1_swedish_ci weight string (0xC0->0x41, 0x6A->0x4A, 0x6B->0x4B, +--echo # 0xD2->0x4F, 0x7A->0x5A). A UUID is stored as a byte permutation of the +--echo # value shown, which does not affect a byte-wise folding. +--echo # +SELECT WEIGHT_STRING(_latin1 0xC0A8016B9E4D4F7AB3D26A1E8F9C0D1B) = + WEIGHT_STRING(_latin1 0x41A8014B9E4D4F5AB34F4A1E8F9C0D1B) AS uuid_pair_collides, + WEIGHT_STRING(_latin1 0x20010DB80000000000000000C0A8016B) = + WEIGHT_STRING(_latin1 0x20010DB8000000000000000041A8014B) AS inet6_pair_collides, + WEIGHT_STRING(_latin1 0xC0A8016B) = + WEIGHT_STRING(_latin1 0x41A8014B) AS inet4_pair_collides; + +--echo # +--echo # UUID +--echo # +--let $fbt_type = UUID +--let $fbt_value1 = 'c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b' +--let $fbt_value2 = '41a8014b-9e4d-4f5a-b34f-4a1e8f9c0d1b' +--source MDEV-41012-collision.inc + +--echo # +--echo # INET6 +--echo # +--let $fbt_type = INET6 +--let $fbt_value1 = '2001:db8::c0a8:16b' +--let $fbt_value2 = '2001:db8::41a8:14b' +--source MDEV-41012-collision.inc + +--echo # +--echo # INET4 +--echo # +--let $fbt_type = INET4 +--let $fbt_value1 = '192.168.1.107' +--let $fbt_value2 = '65.168.1.75' +--source MDEV-41012-collision.inc diff --git a/mysql-test/suite/galera/t/MDEV-41012.inc b/mysql-test/suite/galera/t/MDEV-41012.inc new file mode 100644 index 0000000000000..edb1430d74cb7 --- /dev/null +++ b/mysql-test/suite/galera/t/MDEV-41012.inc @@ -0,0 +1,67 @@ +# +# Body of galera.MDEV-41012, parameterized by the data type of the foreign key. +# +# $fbt_type - data type of the parent PK / child FK column +# $fbt_value - a literal of that type whose binary representation contains +# bytes that latin1_swedish_ci folds (0xC0, 0x6A, 0x6B, 0xD2 ...) +# + +--connection node_1 +--eval CREATE TABLE p (id $fbt_type PRIMARY KEY, v INT) ENGINE=InnoDB +--eval CREATE TABLE c (id INT AUTO_INCREMENT PRIMARY KEY, p_id $fbt_type NOT NULL, KEY k (p_id), FOREIGN KEY (p_id) REFERENCES p (id)) ENGINE=InnoDB +--eval INSERT INTO p VALUES ($fbt_value, 0) + +--connection node_2 +SET SESSION wsrep_sync_wait = 0; +--let $wait_condition = SELECT COUNT(*) = 1 FROM p +--source include/wait_condition.inc + +# Two appliers, so that the two writesets below *can* be applied in parallel +# if certification does not make them depend on each other. +SET GLOBAL wsrep_slave_threads = 2; + +# Park the first applier on entry to the apply monitor, i.e. before the child +# INSERT is applied and before its FK check locks the parent row. +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_set_sync_point.inc + +--connection node_1 +--eval INSERT INTO c (p_id) VALUES ($fbt_value) + +--connection node_2 +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_wait_sync_point.inc + +# clear the sync point. The second applier must run into the real dependency +--source include/galera_clear_sync_point.inc + +--let $apply_waits = `SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_apply_waits'` + +--connection node_1 +--eval UPDATE p SET v = v + 1 WHERE id = $fbt_value + +--connection node_2 +# Wait until the second applier has blocked in the apply monitor because +--let $wait_condition = SELECT (SELECT VARIABLE_VALUE FROM INFORMATION_SCHEMA.GLOBAL_STATUS WHERE VARIABLE_NAME = 'wsrep_apply_waits') > $apply_waits +--source include/wait_condition.inc + +# the second applier waited instead of applying it in parallel. +SELECT COUNT(*) = 0 AS no_row_modified_in_parallel FROM INFORMATION_SCHEMA.INNODB_TRX WHERE trx_rows_modified > 0; + +# Release the child INSERT +--let $galera_sync_point = apply_monitor_slave_enter_sync +--source include/galera_signal_sync_point.inc + +--let $wait_condition = SELECT COUNT(*) = 1 FROM c +--source include/wait_condition.inc +--let $wait_condition = SELECT v = 1 FROM p +--source include/wait_condition.inc + +SELECT v FROM p; +SELECT COUNT(*) FROM c; + +SET GLOBAL wsrep_slave_threads = DEFAULT; +SET SESSION wsrep_sync_wait = DEFAULT; + +--connection node_1 +DROP TABLE c, p; diff --git a/mysql-test/suite/galera/t/MDEV-41012.test b/mysql-test/suite/galera/t/MDEV-41012.test new file mode 100644 index 0000000000000..06a8c604dd6d1 --- /dev/null +++ b/mysql-test/suite/galera/t/MDEV-41012.test @@ -0,0 +1,40 @@ +# +# MDEV-41012: BF-BF conflict when applying a child INSERT and a parent UPDATE +# in parallel, for a foreign key on a fixed binary data type. +# +# The test sends two write sets having access with the same primary key of fixed +# binary data type, the transactions are: +# * INSERT into the child table havinga reference key for the parent row +# * UPDATE of the parent row having the parent's primary key, +# +# The test orders appliers so that the child INSERT is parked before it +# is applied, the parent UPDATE is replicated after it, and we verify that the +# UPDATE has to wait for the INSERT instead of being applied in parallel. +# +# All three Field_fbt types (UUID, INET4 and INET6) are covered. +# + +--source include/galera_cluster.inc +--source include/have_innodb.inc +--source include/galera_have_debug_sync.inc + +--echo # +--echo # UUID +--echo # +--let $fbt_type = UUID +--let $fbt_value = 'c0a8016b-9e4d-4f7a-b3d2-6a1e8f9c0d1b' +--source MDEV-41012.inc + +--echo # +--echo # INET6 +--echo # +--let $fbt_type = INET6 +--let $fbt_value = '2001:db8::c0a8:16b' +--source MDEV-41012.inc + +--echo # +--echo # INET4 +--echo # +--let $fbt_type = INET4 +--let $fbt_value = '192.168.1.107' +--source MDEV-41012.inc diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index e06ee34828398..e9158347102f1 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -6867,10 +6867,21 @@ wsrep_store_key_val_for_row( /* Character set for the field is defined only to fields whose type is string and real field type is not enum or set. For these fields check - if character set is multi byte. */ - + if character set is multi byte. + + Fields that InnoDB stores as DATA_BINARY, + DATA_FIXBINARY or DATA_BLOB must not be collated + here: their bytes are opaque. Note that data types + implemented on top of Field_fbt (UUID, INET6) report + type() == MYSQL_TYPE_STRING and charset() == + my_charset_numeric (i.e. latin1), while their values + are plain binary and InnoDB maps them to + DATA_FIXBINARY. The condition below mirrors + get_innobase_type_from_mysql_type() */ if (real_type != MYSQL_TYPE_ENUM && real_type != MYSQL_TYPE_SET + && !field->binary() + && field->key_type() != HA_KEYTYPE_BINARY && ( mysql_type == MYSQL_TYPE_VAR_STRING || mysql_type == MYSQL_TYPE_STRING)) {