revision-id: a52809e59c6916ced1cd39172a3ed88030abcf06 (fb-prod201907-51-ga52809e59c6) parent(s): f2b801bd023f169bae5e8a8a88334e5c6f1e788d author: Sergei Petrunia committer: Sergei Petrunia timestamp: 2020-04-20 23:20:00 +0300 message: Issue#1026: MyRocks may return "Can't find record" which is unexpected MyRocks generally uses Snapshot Checking for transactional isolation: read rows through snapshot, if we need to modify a row - get a lock on it and check that it hasn't been modified after snapshot was taken. The exception is unique secondary key check which uses "READ-COMMITED": get a lock on the secondary key value, then scan the latest committed data to see if there are duplicate rows. This works, except for INSERT ... ON DUPLICATE KEY UPDATE, where the SQL layer expects to be able to read the duplicate row if it has got HA_ERR_FOUND_DUPP_KEY error from the storage engine. If it doesn't see it, the user gets a "record not found" error. Fixed this by returning "Snapshot conflict" in the case when the duplicate row is present but is not visible in the current transaction's snapshot. --- mysql-test/suite/rocksdb/r/unique_check.result | 26 +++++++++++- mysql-test/suite/rocksdb/r/unique_sec.result | 4 +- .../suite/rocksdb/r/unique_sec_rev_cf.result | 4 +- mysql-test/suite/rocksdb/t/unique_check.test | 47 +++++++++++++++++++++- mysql-test/suite/rocksdb/t/unique_sec.inc | 4 +- storage/rocksdb/ha_rocksdb.cc | 30 +++++++++++++- 6 files changed, 106 insertions(+), 9 deletions(-) diff --git a/mysql-test/suite/rocksdb/r/unique_check.result b/mysql-test/suite/rocksdb/r/unique_check.result index 47c58725ff9..a689c2d94f3 100644 --- a/mysql-test/suite/rocksdb/r/unique_check.result +++ b/mysql-test/suite/rocksdb/r/unique_check.result @@ -19,7 +19,7 @@ insert into t2 values (1,1,1); begin; insert into t2 values (2,1,2); commit; -ERROR 23000: Duplicate entry '1' for key 'id2' +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict) commit; select * from t2; id id2 value @@ -82,3 +82,27 @@ ERROR 23000: Duplicate entry '1' for key 'PRIMARY' insert into t3 values (1, 1), (1, 1); set @@session.unique_checks = @old_val; drop table t1, t2, t3; +# +# Issue#1026: MyRocks may return "Can't find record" which is unexpected +# +CREATE TABLE t1 ( +id bigint(20) unsigned NOT NULL AUTO_INCREMENT, +a varchar(36) NOT NULL , +b varchar(128) NOT NULL , +c varchar(10240) NOT NULL , +PRIMARY KEY (id), +UNIQUE KEY uniq_idx (a,b) +) ENGINE=ROCKSDB; +insert into t1 values (1,1,1,1), (2,2,2,2); +begin; +set debug_sync='rocksdb.after_unique_pk_check SIGNAL trx_a_sleep WAIT_FOR trx_a_cont'; +insert into t1(a,b,c) values (10,'file_type','trx-a') on duplicate key update c=values(c); +set debug_sync='now WAIT_FOR trx_a_sleep'; +begin; +insert into t1(a,b,c) values (10,'file_type','trx-b') on duplicate key update c=values(c); +commit; +set debug_sync='now SIGNAL trx_a_cont'; +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict) +rollback; +drop table t1; +set debug_sync='RESET'; diff --git a/mysql-test/suite/rocksdb/r/unique_sec.result b/mysql-test/suite/rocksdb/r/unique_sec.result index 93775e7c5f6..1d8209d2f95 100644 --- a/mysql-test/suite/rocksdb/r/unique_sec.result +++ b/mysql-test/suite/rocksdb/r/unique_sec.result @@ -135,9 +135,9 @@ COUNT(*) COMMIT; # When transaction is committed, fail on duplicate key INSERT INTO t1 VALUES (40, 40, 40, 40, 40, 40, 40, 40); -Got one of the listed errors +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict) INSERT INTO t1 VALUES (41, 40, 40, 40, 40, 40, 40, 40); -ERROR 23000: Duplicate entry '40-40-40' for key 'id2_2' +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict) ROLLBACK; SELECT * FROM t1; id1 id2 id3 id4 id5 value1 value2 value3 diff --git a/mysql-test/suite/rocksdb/r/unique_sec_rev_cf.result b/mysql-test/suite/rocksdb/r/unique_sec_rev_cf.result index 0ff55ac8d10..ab31928ffc3 100644 --- a/mysql-test/suite/rocksdb/r/unique_sec_rev_cf.result +++ b/mysql-test/suite/rocksdb/r/unique_sec_rev_cf.result @@ -135,9 +135,9 @@ COUNT(*) COMMIT; # When transaction is committed, fail on duplicate key INSERT INTO t1 VALUES (40, 40, 40, 40, 40, 40, 40, 40); -Got one of the listed errors +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict) INSERT INTO t1 VALUES (41, 40, 40, 40, 40, 40, 40, 40); -ERROR 23000: Duplicate entry '40-40-40' for key 'id2_2' +ERROR 40001: Deadlock found when trying to get lock; try restarting transaction (snapshot conflict) ROLLBACK; SELECT * FROM t1; id1 id2 id3 id4 id5 value1 value2 value3 diff --git a/mysql-test/suite/rocksdb/t/unique_check.test b/mysql-test/suite/rocksdb/t/unique_check.test index 90dfcb77f72..7c1f295ff30 100644 --- a/mysql-test/suite/rocksdb/t/unique_check.test +++ b/mysql-test/suite/rocksdb/t/unique_check.test @@ -56,7 +56,7 @@ connection con1; commit; connection con2; ---error ER_DUP_ENTRY +--error ER_LOCK_DEADLOCK reap; commit; select * from t2; @@ -170,3 +170,48 @@ insert into t3 values (1, 1), (1, 1); set @@session.unique_checks = @old_val; # cleanup drop table t1, t2, t3; + + +--echo # +--echo # Issue#1026: MyRocks may return "Can't find record" which is unexpected +--echo # + +connect (con1, localhost, root,,); +connection default; + +CREATE TABLE t1 ( + id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + a varchar(36) NOT NULL , + b varchar(128) NOT NULL , + c varchar(10240) NOT NULL , + PRIMARY KEY (id), + UNIQUE KEY uniq_idx (a,b) +) ENGINE=ROCKSDB; + +insert into t1 values (1,1,1,1), (2,2,2,2); + +## TRX A: +begin; +set debug_sync='rocksdb.after_unique_pk_check SIGNAL trx_a_sleep WAIT_FOR trx_a_cont'; +send +insert into t1(a,b,c) values (10,'file_type','trx-a') on duplicate key update c=values(c); + +connection con1; +# TRX B: +set debug_sync='now WAIT_FOR trx_a_sleep'; +begin; +insert into t1(a,b,c) values (10,'file_type','trx-b') on duplicate key update c=values(c); +commit; +set debug_sync='now SIGNAL trx_a_cont'; + + +connection default; +--error ER_LOCK_DEADLOCK +reap; + +disconnect con1; +rollback; +drop table t1; + +set debug_sync='RESET'; + diff --git a/mysql-test/suite/rocksdb/t/unique_sec.inc b/mysql-test/suite/rocksdb/t/unique_sec.inc index 2f11cd3b65a..4aae7571d98 100644 --- a/mysql-test/suite/rocksdb/t/unique_sec.inc +++ b/mysql-test/suite/rocksdb/t/unique_sec.inc @@ -179,9 +179,9 @@ COMMIT; connection con2; --echo # When transaction is committed, fail on duplicate key ---error ER_DUP_ENTRY,ER_LOCK_DEADLOCK +--error ER_LOCK_DEADLOCK INSERT INTO t1 VALUES (40, 40, 40, 40, 40, 40, 40, 40); ---error ER_DUP_ENTRY +--error ER_LOCK_DEADLOCK INSERT INTO t1 VALUES (41, 40, 40, 40, 40, 40, 40, 40); ROLLBACK; diff --git a/storage/rocksdb/ha_rocksdb.cc b/storage/rocksdb/ha_rocksdb.cc index 3a58567d05b..85d6af2cc01 100644 --- a/storage/rocksdb/ha_rocksdb.cc +++ b/storage/rocksdb/ha_rocksdb.cc @@ -9794,9 +9794,36 @@ int ha_rocksdb::check_and_lock_sk(const uint key_id, */ *found = !read_key_exact(kd, iter, all_parts_used, new_slice, row_info.tx->m_snapshot_timestamp); + + int retval= HA_EXIT_SUCCESS; + if (*found) { + /* + There is a duplicate row, but is it visible to the current transaction? + If it is not, a query like INSERT ... ON DUPLICATE KEY UPDATE will fail + when it can't read the duplicate row. + + To avoid this, perform "Snapshot Checking" - check if the duplicate has + seqno that is not visible to the current transaction. + + RocksDB API doesn't have a call for this. The closest is to call + get_for_update(..., value=nullptr) + which will get a lock and do Snapshot Checking. + Acquiring the lock doesn't matter (we've acquired the lock on the + secondary key already), so this call will only do Snapshot Checking: + */ + rocksdb::Slice found_key= iter->key(); + const rocksdb::Status s = + get_for_update(row_info.tx, kd.get_cf(), found_key, nullptr); + + if (!s.ok()) { + DBUG_ASSERT(!s.IsNotFound()); + retval = row_info.tx->set_status_error(table->in_use, s, kd, m_tbl_def, + m_table_handler); + } + } delete iter; - return HA_EXIT_SUCCESS; + return retval; } /** @@ -9827,6 +9854,7 @@ int ha_rocksdb::check_uniqueness_and_lock( rc = HA_EXIT_SUCCESS; } else { rc = check_and_lock_unique_pk(key_id, row_info, &found); + DEBUG_SYNC(ha_thd(), "rocksdb.after_unique_pk_check"); } } else { rc = check_and_lock_sk(key_id, row_info, &found);