mirror of
https://github.com/MariaDB/server.git
synced 2025-01-15 19:42:28 +01:00
b8a6719889
https://jepsen.io/analyses/mysql-8.0.34 highlights that the transaction isolation levels in the InnoDB storage engine do not correspond to any widely accepted definitions, such as "Generalized Isolation Level Definitions" https://pmg.csail.mit.edu/papers/icde00.pdf (PL-1 = READ UNCOMMITTED, PL-2 = READ COMMITTED, PL-2.99 = REPEATABLE READ, PL-3 = SERIALIZABLE). Only READ UNCOMMITTED in InnoDB seems to match the above definition. The issue is that InnoDB does not detect write/write conflicts (Section 4.4.3, Definition 6) in the above. It appears that as soon as we implement write/write conflict detection (SET SESSION innodb_snapshot_isolation=ON), the default isolation level (SET TRANSACTION ISOLATION LEVEL REPEATABLE READ) will become Snapshot Isolation (similar to Postgres), as defined in Section 4.2 of "A Critique of ANSI SQL Isolation Levels", MSR-TR-95-51, June 1995 https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/tr-95-51.pdf Locking reads inside InnoDB used to read the latest committed version, ignoring what should actually be visible to the transaction. The added test innodb.lock_isolation illustrates this. The statement UPDATE t SET a=3 WHERE b=2; is executed in a transaction that was started before a read view or a snapshot of the current transaction was created, and committed before the current transaction attempts to execute UPDATE t SET b=3; If SET innodb_snapshot_isolation=ON is in effect when the second transaction was started, the second transaction will be aborted with the error ER_CHECKREAD. By default (innodb_snapshot_isolation=OFF), the second transaction would execute inconsistently, displaying an incorrect SELECT COUNT(*) FROM t in its read view. If innodb_snapshot_isolation=ON, if an attempt to acquire a lock on a record that does not exist in the current read view is made, an error DB_RECORD_CHANGED (HA_ERR_RECORD_CHANGED, ER_CHECKREAD) will be raised. This error will be treated in the same way as a deadlock: the transaction will be rolled back. lock_clust_rec_read_check_and_lock(): If the current transaction has a read view where the record is not visible and innodb_snapshot_isolation=ON, fail before trying to acquire the lock. row_sel_build_committed_vers_for_mysql(): If innodb_snapshot_isolation=ON, disable the "semi-consistent read" logic that had been implemented by myself on the directions of Heikki Tuuri in order to address https://bugs.mysql.com/bug.php?id=3300 that was motivated by a customer wanting UPDATE to skip locked rows that do not match the WHERE condition. It looks like my changes were included in the MySQL 5.1.5 commit ad126d90e019f223470e73e1b2b528f9007c4532; at that time, employees of Innobase Oy (a recent acquisition of Oracle) had lost write access to the repository. The only reason why we set innodb_snapshot_isolation=OFF by default is backward compatibility with applications, such as the one that motivated the implementation of "semi-consistent read" back in 2005. In a later major release, we can default to innodb_snapshot_isolation=ON. Thanks to Peter Alvaro, Kyle Kingsbury and Alexey Gotsman for their work on https://github.com/jepsen-io/ and to Kyle and Alexey for explanations and some testing of this fix. Thanks to Vladislav Lesin for the initial test for MDEV-26643, as well as reviewing these changes.
155 lines
3.1 KiB
PHP
155 lines
3.1 KiB
PHP
# include/deadlock.inc
|
|
#
|
|
# The variable
|
|
# $engine_type -- storage engine to be tested
|
|
# has to be set before sourcing this script.
|
|
#
|
|
# Last update:
|
|
# 2006-07-26 ML refactoring + print when connection is switched
|
|
# old name was t/innodb-deadlock.test
|
|
# main code went into include/deadlock.inc
|
|
#
|
|
--disable_service_connection
|
|
connect (con1,localhost,root,,);
|
|
connect (con2,localhost,root,,);
|
|
|
|
--disable_warnings
|
|
drop table if exists t1,t2;
|
|
--enable_warnings
|
|
|
|
#
|
|
# Testing of FOR UPDATE
|
|
#
|
|
--disable_view_protocol
|
|
connection con1;
|
|
eval create table t1 (id integer, x integer) engine = $engine_type;
|
|
insert into t1 values(0, 0);
|
|
set autocommit=0;
|
|
SELECT * from t1 where id = 0 FOR UPDATE;
|
|
|
|
connection con2;
|
|
set autocommit=0;
|
|
|
|
# The following query should hang because con1 is locking the record
|
|
--send
|
|
update t1 set x=2 where id = 0;
|
|
--sleep 2
|
|
|
|
connection con1;
|
|
update t1 set x=1 where id = 0;
|
|
select * from t1;
|
|
commit;
|
|
|
|
connection con2;
|
|
reap;
|
|
commit;
|
|
|
|
connection con1;
|
|
select * from t1;
|
|
commit;
|
|
|
|
drop table t1;
|
|
--enable_view_protocol
|
|
#
|
|
# Testing of FOR UPDATE
|
|
#
|
|
--disable_view_protocol
|
|
connection con1;
|
|
eval create table t1 (id integer, x integer) engine = $engine_type;
|
|
eval create table t2 (b integer, a integer) engine = $engine_type;
|
|
insert into t1 values(0, 0), (300, 300);
|
|
insert into t2 values(0, 10), (1, 20), (2, 30);
|
|
commit;
|
|
set autocommit=0;
|
|
select * from t2;
|
|
update t2 set a=100 where b=(SELECT x from t1 where id = b FOR UPDATE);
|
|
select * from t2;
|
|
select * from t1;
|
|
|
|
connection con2;
|
|
set autocommit=0;
|
|
|
|
# The following query should hang because con1 is locking the record
|
|
--send
|
|
update t1 set x=2 where id = 0;
|
|
--sleep 2
|
|
|
|
connection con1;
|
|
update t1 set x=1 where id = 0;
|
|
select * from t1;
|
|
commit;
|
|
|
|
connection con2;
|
|
reap;
|
|
commit;
|
|
|
|
connection con1;
|
|
select * from t1;
|
|
commit;
|
|
|
|
drop table t1, t2;
|
|
eval create table t1 (id integer, x integer) engine = $engine_type;
|
|
eval create table t2 (b integer, a integer) engine = $engine_type;
|
|
insert into t1 values(0, 0), (300, 300);
|
|
insert into t2 values(0, 0), (1, 20), (2, 30);
|
|
commit;
|
|
|
|
connection con1;
|
|
select a,b from t2 UNION (SELECT id, x from t1 FOR UPDATE);
|
|
select * from t2;
|
|
select * from t1;
|
|
|
|
connection con2;
|
|
|
|
# The following query should hang because con1 is locking the record
|
|
update t2 set a=2 where b = 0;
|
|
--send
|
|
update t1 set x=2 where id = 0;
|
|
--sleep 2
|
|
|
|
connection con1;
|
|
update t1 set x=1 where id = 0;
|
|
select * from t1;
|
|
commit;
|
|
|
|
connection con2;
|
|
reap;
|
|
commit;
|
|
|
|
connection con1;
|
|
select * from t1;
|
|
commit;
|
|
|
|
# Cleanup
|
|
connection default;
|
|
disconnect con1;
|
|
disconnect con2;
|
|
drop table t1, t2;
|
|
--enable_view_protocol
|
|
--echo End of 4.1 tests
|
|
|
|
#
|
|
# Bug#25164 create table `a` as select * from `A` hangs
|
|
#
|
|
|
|
set default_storage_engine=innodb;
|
|
|
|
--disable_warnings
|
|
drop table if exists a;
|
|
drop table if exists A;
|
|
--enable_warnings
|
|
|
|
create table A (c int);
|
|
insert into A (c) values (0);
|
|
--error 0,ER_LOCK_DEADLOCK,ER_TABLE_EXISTS_ERROR
|
|
create table a as select * from A;
|
|
drop table A;
|
|
|
|
--disable_warnings
|
|
drop table if exists a;
|
|
--enable_warnings
|
|
|
|
set default_storage_engine=default;
|
|
|
|
--echo End of 5.0 tests.
|
|
--enable_service_connection
|