Per-database READ ONLY is not enforced for UPDATE, multi-table DELETE, BATCH ... UPDATE or CREATE SEQUENCE; writes commit silently against a locked database
Bug Report
1. Minimal reproduce step (Required)
Branch feature/release-8.5-read_only @ 48e340f5bf7fe4a19c2763fa2bf41df3c013536a (tracking issue #62122). Pure SQL, no special topology — a single TiDB with the feature is enough.
The one thing that matters: connect with NO default database (mysql -h H -P P -u root, no db argument). That is the normal shape for connection-pooled applications, ORMs and migration tools that fully qualify table names.
CREATE DATABASE d;
CREATE TABLE d.t(id INT PRIMARY KEY, v VARCHAR(16));
CREATE TABLE d.u(id INT PRIMARY KEY, v VARCHAR(16));
INSERT INTO d.t VALUES (1,'A'),(2,'A'),(3,'A');
INSERT INTO d.u VALUES (1,'A'),(2,'A'),(3,'A');
ALTER DATABASE d READ ONLY = 1;
-- confirm: SELECT OPTIONS FROM INFORMATION_SCHEMA.SCHEMATA_EXTENSIONS WHERE SCHEMA_NAME='d';
-- -> READ ONLY=1Then, from a session with no current database:
UPDATE d.t SET v='X' WHERE id=1; -- SUCCEEDS <- should be ERROR 3989
UPDATE d.t SET t.v='X' WHERE id=1; -- SUCCEEDS <- should be ERROR 3989
DELETE a FROM d.t a; -- SUCCEEDS, EMPTIES THE TABLE
CREATE SEQUENCE d.s1; -- SUCCEEDS, object created in a locked schema
BATCH ON id LIMIT 100 UPDATE d.t SET v='B'; -- SUCCEEDS-- controls, all correctly rejected with ERROR 3989:
INSERT INTO d.t VALUES (9,'x');
DELETE FROM d.t WHERE id=1;
UPDATE d.t SET d.t.v='X' WHERE id=1;
TRUNCATE TABLE d.t; ALTER TABLE d.t ADD COLUMN c1 INT; DROP TABLE d.t;Writing to a locked database from a session sitting in a different writable database also bypasses, which is the common pooled-connection shape:
CREATE DATABASE rw; CREATE TABLE rw.a(id INT PRIMARY KEY, v VARCHAR(16));
INSERT INTO rw.a VALUES (1,'A'),(2,'A'),(3,'A');
USE rw;
UPDATE rw.a JOIN d.t ON a.id=t.id SET t.v='HIT'; -- SUCCEEDS, d.t rows modified
INSERT INTO d.t VALUES (7,'A'); -- ERROR 3989 in the same session2. What did you expect to see? (Required)
A database marked READ ONLY = 1 should reject every statement that modifies it, with ERROR 3989, regardless of the session's current database and regardless of how the target is spelled.
3. What did you see instead (Required)
Writes commit. They are durable, visible on every node, and the database continues to report READ ONLY=1 throughout.
4. What is your TiDB version? (Required)
Release Version: v8.4.0-feature
Edition: Community
Git Commit Hash: 48e340f5bf
Git Branch: feature/release-8.5-read_only
GoVersion: go1.23.12Source: pingcap/tidb