mariadb/mysql-test/main/mdl.test
Sergey Vojtovich 18985d8471 MDEV-19749 - MDL scalability regression after backup locks
Statements that intend to modify data have to acquire protection
against ongoing backup. Prior to backup locks, protection against
FTWRL was acquired in form of 2 shared metadata locks of GLOBAL
(global read lock) and COMMIT namespaces. These two namespaces
were separate entities, they didn't share data structures and
locking primitives. And thus they were separate contention
points.

With backup locks, introduced by 7a9dfdd, these namespaces were
combined into a single BACKUP namespace. It became a single
contention point, which doubled load on BACKUP namespace data
structures and locking primitives compared to GLOBAL and COMMIT
namespaces. In other words system throughput has halved.

MDL fast lanes solve this problem by allowing multiple contention
points for single MDL_lock. Fast lane is scalable multi-instance
registry for leightweight locks. Internally it is just a list of
granted tickets, close counter and a mutex.

Number of fast lanes (or contention points) is defined by the
metadata_locks_instances system variable. Value of 1 disables fast
lanes and lock requests are served by conventional MDL_lock data
structures.

Since fast lanes allow arbitrary number of contention points, they
outperform pre-backup locks GLOBAL and COMMIT.

Fast lanes are enabled only for BACKUP namespace. Support for other
namespaces is to be implemented separately.

Lock types are divided in 2 categories: lightweight and heavyweight.

Lightweight lock types represent DML: MDL_BACKUP_DML,
MDL_BACKUP_TRANS_DML, MDL_BACKUP_SYS_DML, MDL_BACKUP_DDL,
MDL_BACKUP_ALTER_COPY, MDL_BACKUP_COMMIT. They are fully compatible
with each other. Normally served by corresponding fast lane, which is
determined by thread_id % metadata_locks_instances.

Heavyweight lock types represent ongoing backup: MDL_BACKUP_START,
MDL_BACKUP_FLUSH, MDL_BACKUP_WAIT_FLUSH, MDL_BACKUP_WAIT_DDL,
MDL_BACKUP_WAIT_COMMIT, MDL_BACKUP_FTWRL1, MDL_BACKUP_FTWRL2,
MDL_BACKUP_BLOCK_DDL. These locks are always served by conventional
MDL_lock data structures. Whenever such lock is requested, fast
lanes are closed and all tickets registered in fast lanes are
moved to conventional MDL_lock data structures. Until such locks
are released or aborted, lightweight lock requests are served by
conventional MDL_lock data structures.

Strictly speaking moving tickets from fast lanes to conventional
MDL_lock data structures is not required. But it allows to reduce
complexity and keep intact methods like: MDL_lock::visit_subgraph(),
MDL_lock::notify_conflicting_locks(), MDL_lock::reschedule_waiters(),
MDL_lock::can_grant_lock().

It is not even required to register tickets in fast lanes. They
can be implemented basing on an atomic variable that holds two
counters: granted lightweight locks and granted/waiting heavyweight
locks. Similarly to MySQL solution, which roughly speaking has
"single atomic fast lane". However it appears to be it won't bring
any better performance, while code complexity is going to be much
higher.
2025-07-15 23:19:06 +04:00

139 lines
4.3 KiB
Text

--source include/have_metadata_lock_info.inc
--source include/have_innodb.inc
--echo #
--echo # MDEV-12882 - Assertion `mdl_ticket->m_type == MDL_SHARED_UPGRADABLE ||
--echo # mdl_ticket->m_type == MDL_SHARED_NO_WRITE ||
--echo # mdl_ticket->m_type == MDL_SHARED_NO_READ_WRITE ||
--echo # mdl_ticket->m_type == MDL_SHARED_READ'
--echo # failed in MDL_context::upgrade_shared_lock
--echo #
--disable_service_connection
CREATE TABLE t1(a INT) ENGINE=InnoDB;
CREATE TABLE t3(a INT) ENGINE=myisam;
LOCK TABLES t1 WRITE CONCURRENT, t1 AS t2 READ;
SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info
WHERE TABLE_NAME NOT LIKE 'innodb_%_stats';
UNLOCK TABLES;
LOCK TABLES t1 AS t2 READ, t1 WRITE CONCURRENT;
SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info
WHERE TABLE_NAME NOT LIKE 'innodb_%_stats';
UNLOCK TABLES;
LOCK TABLES t1 WRITE CONCURRENT, t3 WRITE;
--sorted_result
SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info
WHERE TABLE_NAME NOT LIKE 'innodb_%_stats';
UNLOCK TABLES;
LOCK TABLES t3 WRITE, t1 WRITE CONCURRENT;
--sorted_result
SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info
WHERE TABLE_NAME NOT LIKE 'innodb_%_stats';
UNLOCK TABLES;
LOCK TABLES t1 WRITE, mysql.user WRITE;
SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info
WHERE TABLE_NAME NOT LIKE 'innodb_%_stats';
UNLOCK TABLES;
--error ER_CANT_LOCK_LOG_TABLE
LOCK TABLES mysql.general_log WRITE;
# The following may work in embedded server
--error 0,ER_DBACCESS_DENIED_ERROR
LOCK TABLES t1 WRITE,information_schema.tables READ;
UNLOCK TABLES;
DROP TABLE t1,t3;
--echo #
--echo # Check MDL locks taken for different kind of tables by open
--echo #
CREATE TABLE t1(a INT) stats_persistent=0, ENGINE=InnoDB;
CREATE TABLE t3(a INT) ENGINE=myisam;
connect(purge_control,localhost,root,,);
START TRANSACTION WITH CONSISTENT SNAPSHOT;
connect (locker,localhost,root,,);
connection default;
FLUSH TABLES WITH READ LOCK;
connection locker;
--send insert into t1 values (1)
connection default;
# Wait till above update gets blocked on a user lock.
let $wait_condition=
select count(*) > 0 from information_schema.processlist
where state = "Waiting for backup lock";
--source include/wait_condition.inc
connection default;
SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info
WHERE TABLE_NAME NOT LIKE 'innodb_%_stats';
unlock tables;
connection locker;
--reap
unlock tables;
connection default;
FLUSH TABLES WITH READ LOCK;
connection locker;
--send insert into t3 values (2)
connection default;
# Wait till above update gets blocked on a user lock.
let $wait_condition=
select count(*) > 0 from information_schema.processlist
where state = "Waiting for backup lock";
--source include/wait_condition.inc
connection default;
SELECT LOCK_MODE, LOCK_TYPE, TABLE_SCHEMA, TABLE_NAME FROM information_schema.metadata_lock_info
WHERE TABLE_NAME NOT LIKE 'innodb_%_stats';
unlock tables;
connection locker;
--reap
unlock tables;
disconnect purge_control;
connection default;
disconnect locker;
DROP TABLE t1,t3;
--enable_service_connection
--echo #
--echo # MDEV-28820 MyISAM wrong server status flags
--echo #
--disable_view_protocol
# MyISAM alone doesn't start a transaction or takes transactional MDL
create table t1 (a int);
set autocommit=0;
select @@in_transaction;
select * from t1;
select @@in_transaction;
connect foo,localhost,root;
drop table t1;
connection default;
set autocommit=1;
# MyISAM in a transaction (started by InnoDB) takes transactional MDL all right
create table t1 (a int);
create table t2 (b int) engine=innodb;
set autocommit=0;
select @@in_transaction;
select * from t2;
select @@in_transaction;
select * from t1;
connection foo;
send drop table t1;
connection default;
let $wait_condition=
select count(*) > 0 from information_schema.processlist
where state = "Waiting for table metadata lock";
--source include/wait_condition.inc
select * from t1;
commit;
connection foo;
reap;
disconnect foo;
connection default;
set autocommit=default;
drop table t2;
--enable_view_protocol
--echo #
--echo # End of 10.4 tests
--echo #