From 0b6edbe865c7d05b0c9922d308953f3f20b8b443 Mon Sep 17 00:00:00 2001 From: Mohammad Tafzeel Shams Date: Tue, 23 Jun 2026 15:26:01 +0530 Subject: [PATCH] MDEV-26123: Always mark spatial index reads as locking A SELECT query could fail with ER_READ_ONLY_TRANSACTION when search on spatial index is done after search on non-spatial index. R-tree index search always require page locks. Previously, trx->will_lock was only set for spatial indexes when the transaction had not yet been started. If a transaction was already active due to a prior non-spatial index access, index_read() for spatial index returned HA_ERR_READ_ONLY_TRANSACTION instead of marking the transaction as locking. Fix: - ha_innobase::index_read() : Set trx->will_lock unconditionally for spatial index, ensuring that R-tree searches can acquire the required page locks regardless of transaction state. --- .../suite/innodb_gis/r/rtree_search.result | 36 ++++++++++++++++ .../suite/innodb_gis/t/rtree_search.test | 42 +++++++++++++++++++ storage/innobase/handler/ha_innodb.cc | 8 +--- 3 files changed, 80 insertions(+), 6 deletions(-) diff --git a/mysql-test/suite/innodb_gis/r/rtree_search.result b/mysql-test/suite/innodb_gis/r/rtree_search.result index d8a8d209cd36a..e6ca3c3533727 100644 --- a/mysql-test/suite/innodb_gis/r/rtree_search.result +++ b/mysql-test/suite/innodb_gis/r/rtree_search.result @@ -250,3 +250,39 @@ count(*) drop procedure curdemo; drop table t3; drop table t1; +# +# MDEV 26123 : Always mark spatial index reads as locking +# +SET @old_autocommit = @@autocommit; +SET @old_tx_read_only = @@session.tx_read_only; +CREATE TABLE t1 ( +c1 INT PRIMARY KEY, +category INT NOT NULL, +p POINT NOT NULL, +KEY idx_category (category), +SPATIAL INDEX sp_idx_p (p) +) ENGINE=InnoDB; +INSERT INTO t1 VALUES +(1, 10, POINT(1,2)); +CREATE PROCEDURE populate_t1() +BEGIN +DECLARE i INT DEFAULT 2; +WHILE i <= 5000 DO +INSERT INTO t1 VALUES +(i, 20, POINT(5,5)); +SET i = i + 1; +END WHILE; +END | +CALL populate_t1(); +SELECT COUNT(*) FROM t1 WHERE ST_Contains(t1.p, (SELECT p FROM t1 WHERE category = 10)); +COUNT(*) +1 +SET SESSION TRANSACTION READ ONLY; +SET autocommit = OFF; +SELECT COUNT(*) FROM t1 WHERE category = (SELECT category FROM t1 WHERE ST_EQUALS(p, POINT(1,2))); +COUNT(*) +1 +SET autocommit = @old_autocommit; +SET SESSION tx_read_only = @old_tx_read_only; +DROP PROCEDURE populate_t1; +DROP TABLE t1; diff --git a/mysql-test/suite/innodb_gis/t/rtree_search.test b/mysql-test/suite/innodb_gis/t/rtree_search.test index 6bbd84a25cf0e..290b6634825a1 100644 --- a/mysql-test/suite/innodb_gis/t/rtree_search.test +++ b/mysql-test/suite/innodb_gis/t/rtree_search.test @@ -139,3 +139,45 @@ drop procedure curdemo; drop table t3; drop table t1; +--echo # +--echo # MDEV 26123 : Always mark spatial index reads as locking +--echo # +SET @old_autocommit = @@autocommit; +SET @old_tx_read_only = @@session.tx_read_only; + +CREATE TABLE t1 ( + c1 INT PRIMARY KEY, + category INT NOT NULL, + p POINT NOT NULL, + KEY idx_category (category), + SPATIAL INDEX sp_idx_p (p) +) ENGINE=InnoDB; + +INSERT INTO t1 VALUES +(1, 10, POINT(1,2)); + +DELIMITER |; +CREATE PROCEDURE populate_t1() +BEGIN + DECLARE i INT DEFAULT 2; + + WHILE i <= 5000 DO + INSERT INTO t1 VALUES + (i, 20, POINT(5,5)); + SET i = i + 1; + END WHILE; +END | +DELIMITER ;| +CALL populate_t1(); + +SELECT COUNT(*) FROM t1 WHERE ST_Contains(t1.p, (SELECT p FROM t1 WHERE category = 10)); + +SET SESSION TRANSACTION READ ONLY; +SET autocommit = OFF; +SELECT COUNT(*) FROM t1 WHERE category = (SELECT category FROM t1 WHERE ST_EQUALS(p, POINT(1,2))); + +# Cleanup +SET autocommit = @old_autocommit; +SET SESSION tx_read_only = @old_tx_read_only; +DROP PROCEDURE populate_t1; +DROP TABLE t1; diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 02649112b8cd2..2ddcb8c93ff60 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -9012,12 +9012,8 @@ ha_innobase::index_read( /* For R-Tree index, we will always place the page lock to pages being searched */ - if (index->is_spatial() && !m_prebuilt->trx->will_lock) { - if (trx_state != TRX_STATE_NOT_STARTED) { - DBUG_RETURN(HA_ERR_READ_ONLY_TRANSACTION); - } else { - m_prebuilt->trx->will_lock = true; - } + if (index->is_spatial()) { + m_prebuilt->trx->will_lock = true; } /* Note that if the index for which the search template is built is not