Skip to content

Commit 7d19786

Browse files
committed
Bug#33974581 Updating a non-PK table row on Master stops SQL Thread on
Slave Problem: Update of table with hidden key stops applier Solution: When Applier process changes to a hidden key table it need to read from NDB instead of just defining another update or write. Before the read can take place, any already defined operations need to be sent to NDB. This is done both in order to "read your own writes" as well as handle constraint violation and missing row errors in the same way as if the table with hidden key was not changed (in the epoch transaction). Change-Id: I548ca38e30a065ad67901e293f6351ba4acc1f42
1 parent edd248f commit 7d19786

File tree

3 files changed

+230
-1
lines changed

3 files changed

+230
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
include/master-slave.inc
2+
Warnings:
3+
Note #### Sending passwords in plain text without SSL/TLS is extremely insecure.
4+
Note #### Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information.
5+
[connection master]
6+
#
7+
# Bug#33974581 Update of table with hidden key stops applier
8+
#
9+
# When Applier process changes to a hidden key table it need to read
10+
# from NDB instead of just defining another update or write. Before the
11+
# read can take place, any already defined operations need to be sent to
12+
# NDB. This is done both in order to "read your own writes" as well as
13+
# handle constraint violation and missing row errors in the same way as
14+
# if the table with hidden key was not changed.
15+
#
16+
#
17+
# 1) This test shows "read your own write" for table with hidden pk.
18+
# Since the table has hidden pk the UPDATE will need to read rows
19+
# from NDB, find the ones that match the condition and then update those.
20+
# In order to make it possible for NDB to return the first INSERT it
21+
# need to be prepared/flushed before starting to read.
22+
#
23+
[connection master]
24+
CREATE TABLE t1 (a INT, b INT) engine = NDB;
25+
INSERT INTO t1 VALUES (1, 1);
26+
UPDATE t1 SET a=NULL where b=1;
27+
include/sync_slave_sql_with_master.inc
28+
[connection master]
29+
DROP TABLE t1;
30+
include/sync_slave_sql_with_master.inc
31+
#
32+
# 2) This tests show that applying changes to table with hidden pk
33+
# ignore error(s) from previously defined changes (in the same epoch
34+
# transaction) as they should.
35+
#
36+
[connection master]
37+
CREATE TABLE t1 (a INT PRIMARY KEY, b INT) engine = NDB;
38+
CREATE TABLE t2_hidden_pk (a INT, b INT) engine = NDB;
39+
CREATE TABLE t3_hidden_pk_unique (a INT, b INT, unique(b)) engine = NDB;
40+
CREATE TABLE t4_hidden_pk_index (a INT, b INT, index(b)) engine = NDB;
41+
INSERT INTO t1 VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
42+
INSERT INTO t2_hidden_pk VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
43+
INSERT INTO t3_hidden_pk_unique VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
44+
INSERT INTO t4_hidden_pk_index VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
45+
include/sync_slave_sql_with_master.inc
46+
[connection slave]
47+
## Delete rows on replica to make it possible to change something
48+
## on the source that would then fail when applied on the replica.
49+
DELETE FROM t1 WHERE a = 5;
50+
DELETE FROM t2_hidden_pk WHERE a = 5;
51+
DELETE FROM t3_hidden_pk_unique WHERE a = 5;
52+
DELETE FROM t4_hidden_pk_index WHERE a = 5;
53+
## Case 1, pk table (the "normal" case)
54+
## - update row that does not exist on replica
55+
## - update pk table -> direct update
56+
[connection master]
57+
BEGIN;
58+
UPDATE t1 SET b = 0 WHERE a = 5;
59+
UPDATE t1 SET b = 10 WHERE a = 1;
60+
COMMIT;
61+
include/sync_slave_sql_with_master.inc
62+
## Case 2, hidden pk table
63+
## - update row that does not exist on replica
64+
## - update hidden pk table -> uses scan
65+
[connection master]
66+
BEGIN;
67+
UPDATE t1 SET b = 0 WHERE a = 5;
68+
UPDATE t2_hidden_pk SET b = 10 WHERE a = 1;
69+
COMMIT;
70+
include/sync_slave_sql_with_master.inc
71+
## Case 3, hidden pk table with unique index
72+
## - update row that does not exist on replica
73+
## - update hidden pk table with unique index -> uses index scan
74+
[connection master]
75+
BEGIN;
76+
UPDATE t1 SET b = 0 WHERE a = 5;
77+
UPDATE t3_hidden_pk_unique SET a = 30 WHERE b < 3;
78+
COMMIT;
79+
include/sync_slave_sql_with_master.inc
80+
## Case 4, hidden pk table with index
81+
## - update row that does not exist on replica
82+
## - update hidden pk table with index -> uses index scan
83+
[connection master]
84+
BEGIN;
85+
UPDATE t1 SET b = 0 WHERE a = 5;
86+
UPDATE t4_hidden_pk_index SET a = 40 WHERE b < 4;
87+
COMMIT;
88+
include/sync_slave_sql_with_master.inc
89+
# Cleanup
90+
[connection master]
91+
DROP TABLE t1, t2_hidden_pk, t3_hidden_pk_unique, t4_hidden_pk_index;
92+
include/sync_slave_sql_with_master.inc
93+
include/rpl_end.inc
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
--source include/have_ndb.inc
2+
--source include/have_binlog_format_mixed_or_row.inc
3+
--source suite/ndb_rpl/ndb_master-slave.inc
4+
5+
6+
--echo #
7+
--echo # Bug#33974581 Update of table with hidden key stops applier
8+
--echo #
9+
--echo # When Applier process changes to a hidden key table it need to read
10+
--echo # from NDB instead of just defining another update or write. Before the
11+
--echo # read can take place, any already defined operations need to be sent to
12+
--echo # NDB. This is done both in order to "read your own writes" as well as
13+
--echo # handle constraint violation and missing row errors in the same way as
14+
--echo # if the table with hidden key was not changed.
15+
--echo #
16+
17+
--echo #
18+
--echo # 1) This test shows "read your own write" for table with hidden pk.
19+
--echo # Since the table has hidden pk the UPDATE will need to read rows
20+
--echo # from NDB, find the ones that match the condition and then update those.
21+
--echo # In order to make it possible for NDB to return the first INSERT it
22+
--echo # need to be prepared/flushed before starting to read.
23+
--echo #
24+
--source include/rpl_connection_master.inc
25+
CREATE TABLE t1 (a INT, b INT) engine = NDB;
26+
INSERT INTO t1 VALUES (1, 1);
27+
UPDATE t1 SET a=NULL where b=1;
28+
--source include/sync_slave_sql_with_master.inc
29+
30+
--source include/rpl_connection_master.inc
31+
DROP TABLE t1;
32+
--source include/sync_slave_sql_with_master.inc
33+
34+
--echo #
35+
--echo # 2) This tests show that applying changes to table with hidden pk
36+
--echo # ignore error(s) from previously defined changes (in the same epoch
37+
--echo # transaction) as they should.
38+
--echo #
39+
--source include/rpl_connection_master.inc
40+
CREATE TABLE t1 (a INT PRIMARY KEY, b INT) engine = NDB;
41+
CREATE TABLE t2_hidden_pk (a INT, b INT) engine = NDB;
42+
CREATE TABLE t3_hidden_pk_unique (a INT, b INT, unique(b)) engine = NDB;
43+
CREATE TABLE t4_hidden_pk_index (a INT, b INT, index(b)) engine = NDB;
44+
INSERT INTO t1 VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
45+
INSERT INTO t2_hidden_pk VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
46+
INSERT INTO t3_hidden_pk_unique VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
47+
INSERT INTO t4_hidden_pk_index VALUES (1,1), (2,2), (3,3), (4,4), (5,5);
48+
--source include/sync_slave_sql_with_master.inc
49+
50+
--source include/rpl_connection_slave.inc
51+
--echo ## Delete rows on replica to make it possible to change something
52+
--echo ## on the source that would then fail when applied on the replica.
53+
DELETE FROM t1 WHERE a = 5;
54+
DELETE FROM t2_hidden_pk WHERE a = 5;
55+
DELETE FROM t3_hidden_pk_unique WHERE a = 5;
56+
DELETE FROM t4_hidden_pk_index WHERE a = 5;
57+
58+
--echo ## Case 1, pk table (the "normal" case)
59+
--echo ## - update row that does not exist on replica
60+
--echo ## - update pk table -> direct update
61+
--source include/rpl_connection_master.inc
62+
BEGIN;
63+
UPDATE t1 SET b = 0 WHERE a = 5;
64+
UPDATE t1 SET b = 10 WHERE a = 1;
65+
COMMIT;
66+
--source include/sync_slave_sql_with_master.inc
67+
68+
--echo ## Case 2, hidden pk table
69+
--echo ## - update row that does not exist on replica
70+
--echo ## - update hidden pk table -> uses scan
71+
--source include/rpl_connection_master.inc
72+
BEGIN;
73+
UPDATE t1 SET b = 0 WHERE a = 5;
74+
UPDATE t2_hidden_pk SET b = 10 WHERE a = 1;
75+
COMMIT;
76+
--source include/sync_slave_sql_with_master.inc
77+
78+
--echo ## Case 3, hidden pk table with unique index
79+
--echo ## - update row that does not exist on replica
80+
--echo ## - update hidden pk table with unique index -> uses index scan
81+
--source include/rpl_connection_master.inc
82+
BEGIN;
83+
UPDATE t1 SET b = 0 WHERE a = 5;
84+
UPDATE t3_hidden_pk_unique SET a = 30 WHERE b < 3;
85+
COMMIT;
86+
--source include/sync_slave_sql_with_master.inc
87+
88+
--echo ## Case 4, hidden pk table with index
89+
--echo ## - update row that does not exist on replica
90+
--echo ## - update hidden pk table with index -> uses index scan
91+
--source include/rpl_connection_master.inc
92+
BEGIN;
93+
UPDATE t1 SET b = 0 WHERE a = 5;
94+
UPDATE t4_hidden_pk_index SET a = 40 WHERE b < 4;
95+
COMMIT;
96+
--source include/sync_slave_sql_with_master.inc
97+
98+
--echo # Cleanup
99+
--source include/rpl_connection_master.inc
100+
DROP TABLE t1, t2_hidden_pk, t3_hidden_pk_unique, t4_hidden_pk_index;
101+
--source include/sync_slave_sql_with_master.inc
102+
103+
--source include/rpl_end.inc

sql/ha_ndbcluster.cc

+34-1
Original file line numberDiff line numberDiff line change
@@ -7382,6 +7382,22 @@ int ha_ndbcluster::index_init(uint index, bool sorted)
73827382
{
73837383
DBUG_ENTER("ha_ndbcluster::index_init");
73847384
DBUG_PRINT("enter", ("index: %u sorted: %d", index, sorted));
7385+
7386+
if (m_thd_ndb->is_slave_thread()) {
7387+
if (table_share->primary_key == MAX_KEY && // hidden pk
7388+
m_thd_ndb->m_unsent_bytes) {
7389+
// Applier starting read from table with hidden pk when there are already
7390+
// defined operations that need to be prepared in order to "read your own
7391+
// writes" as well as handle errors uniformly.
7392+
DBUG_PRINT("info", ("Prepare already defined operations before read"));
7393+
const bool IGNORE_NO_KEY = true;
7394+
if (execute_no_commit(m_thd_ndb, m_thd_ndb->trans, IGNORE_NO_KEY) != 0) {
7395+
no_uncommitted_rows_execute_failure();
7396+
DBUG_RETURN(ndb_err(m_thd_ndb->trans));
7397+
}
7398+
}
7399+
}
7400+
73857401
active_index= index;
73867402
m_sorted= sorted;
73877403
/*
@@ -7642,7 +7658,23 @@ int ha_ndbcluster::rnd_init(bool scan)
76427658

76437659
if ((error= close_scan()))
76447660
DBUG_RETURN(error);
7645-
index_init(table_share->primary_key, 0);
7661+
7662+
if (m_thd_ndb->is_slave_thread()) {
7663+
if (table_share->primary_key == MAX_KEY && m_thd_ndb->m_unsent_bytes) {
7664+
// Applier starting scan on keyless table and there are unsent bytes,
7665+
// flush the "batch" since most likely m_ignore_no_key was ON when these
7666+
// operations were defined
7667+
const bool FORCE_IGNORE_NO_KEY = true;
7668+
if (execute_no_commit(m_thd_ndb, m_thd_ndb->trans,
7669+
FORCE_IGNORE_NO_KEY) != 0) {
7670+
no_uncommitted_rows_execute_failure();
7671+
DBUG_RETURN(ndb_err(m_thd_ndb->trans));
7672+
}
7673+
}
7674+
}
7675+
7676+
if ((error = index_init(table_share->primary_key, 0)))
7677+
DBUG_RETURN(error);
76467678
DBUG_RETURN(0);
76477679
}
76487680

@@ -16320,6 +16352,7 @@ int ha_ndbcluster::multi_range_read_init(RANGE_SEQ_IF *seq_funcs,
1632016352
{
1632116353
int error;
1632216354
DBUG_ENTER("ha_ndbcluster::multi_range_read_init");
16355+
assert(!m_thd_ndb->is_slave_thread()); // mrr not used by applier
1632316356

1632416357
/*
1632516358
If supplied buffer is smaller than needed for just one range, we cannot do

0 commit comments

Comments
 (0)