Bug #117337 Transaction Commit Allowed on Blackhole Table After read_only=1 is Enabled
Submitted: 30 Jan 12:09 Modified: 31 Jan 13:57
Reporter: Aftab Khan Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Storage Engines Severity:S2 (Serious)
Version:8.0.34-26, 8.0.41 OS:Any
Assigned to: CPU Architecture:Any
Tags: blackhole, read_only

[30 Jan 12:09] Aftab Khan
Description:
A transaction modifying a Blackhole table can still be committed even after read_only mode is enabled globally. This violates the expected behaviour where no modifications should be allowed once read_only=1.

How to repeat:
### **MySQL Version: 8.0.34-26
### **Steps to Reproduce**
#### **1. Create the test database and Blackhole table**

```sql CREATE DATABASE foo;
USE foo;
CREATE TABLE t1 (a INT, b INT) ENGINE=BLACKHOLE;
GRANT ALL PRIVILEGES ON foo.* TO 'test_user'@'%';
FLUSH PRIVILEGES;

### First MySQL Session
###Verify Read-Only Mode is Disabled
```sql
mysql>  select @@global.read_only, @@read_only;
+--------------------+-------------+
| @@global.read_only | @@read_only |
+--------------------+-------------+
|                  1 |           1 |
+--------------------+-------------+
1 row in set (0.00 sec)

### Start a Transaction and Insert Data
```sql
BEGIN;
INSERT INTO t1 VALUES (9999, 3);
INSERT INTO t1 VALUES (9998, 3);

### Enable read-only mode in a second session
```sql
SET GLOBAL read_only = 1;
SELECT @@global.read_only;
-- Expected Output:
-- +--------------------+
-- | @@global.read_only |
-- +--------------------+
-- |                  1 |
-- +--------------------+

### Commit the transaction in the first session
```sql
COMMIT;
-- Unexpectedly succeeds

### Try inserting a new row after the commit
```sql
INSERT INTO t1 VALUES (9997,2);
-- Expected Error:
-- ERROR 1290 (HY000): The MySQL server is running with the --read-only option so it cannot execute this statement

(Write operations are blocked after the commit)

Suggested fix:
### Expected Behaviour:
The commit should fail once read_only=1 is set.

###Observed Behavior:
The transaction committed successfully, even though read_only was enabled.
[30 Jan 13:00] MySQL Verification Team
Hi Mr. Khan,

Thank you for your bug report.

However, this storage engine is no longer supported.

You can take a look at the category menu and you will see that Blackhole storage engine is no longer listed.

Unsupported.
[30 Jan 15:50] Tsubasa Tanaka
> However, this storage engine is no longer supported.

It's my first time to hear it. Is it official opoinion?

Therefore, you have to update Reference Manual and nutshell -- BLACKHE Storage Engine is deprecated and already unsupported.

https://dev.mysql.com/doc/refman/8.4/en/blackhole-storage-engine.html

> You can take a look at the category menu and you will see that Blackhole storage engine is no longer listed.

I never found such a category from 2012. Was it really exsited?

https://bugs.mysql.com/search.php?cmd=display&status[]=All&severity=all&search_for=%2Bblac...
[31 Jan 7:55] MySQL Verification Team
Hello Tanaka San,

Agree with you, BLACKHOLE Storage Engine is supported. Thank you.

Hello Aftab,

Thank you for the report and feedback.
I quickly tried to reproduce and confirmed that blackhole SE honours the read_only setting.
Quoting from our manual "If you change a global system variable, the value is remembered and used to initialize the session value for new sessions until you change the variable to a different value or the server exits. The change is visible to any client that accesses the global value. However, the change affects the corresponding session value only for clients that connect after the change. The global variable change does not affect the session value for any current client sessions (not even the session within which the global value change occurs)."

===================

-- release/opt
BugNumber=117337
rm -rf $BugNumber/
bin/mysqld --no-defaults --initialize-insecure --basedir=$PWD --datadir=$PWD/$BugNumber --log-error-verbosity=3
bin/mysqld_safe --no-defaults --mysqld-version='' --basedir=$PWD --datadir=$PWD/$BugNumber --core-file --socket=/tmp/mysql.sock  --port=3306 --log-error=$PWD/$BugNumber/log.err --mysqlx-port=33330 --mysqlx-socket=/tmp/mysql_x_ushastry.sock --log-error-verbosity=3  --secure-file-priv="" --local-infile=1  2>&1 &

-- 

 bin/mysql -uroot -S/tmp/mysql.sock
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 7
Server version: 8.0.41 MySQL Community Server - GPL

Copyright (c) 2000, 2025, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> CREATE DATABASE foo;
Query OK, 1 row affected (0.01 sec)

mysql> USE foo;
Database changed
mysql> CREATE TABLE t1 (a INT, b INT) ENGINE=BLACKHOLE;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE USER 'test_user'@'%';
Query OK, 0 rows affected (0.00 sec)

mysql> GRANT ALL PRIVILEGES ON foo.* TO 'test_user'@'%';
Query OK, 0 rows affected (0.01 sec)

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

-- connect using 'test_user'@'%' - first session

 bin/mysql -utest_user --prompt='Session1>'
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.41 MySQL Community Server - GPL

Copyright (c) 2000, 2025, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

Session1>use foo
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
Session1>select @@global.read_only, @@read_only;
+--------------------+-------------+
| @@global.read_only | @@read_only |
+--------------------+-------------+
|                  0 |           0 |
+--------------------+-------------+
1 row in set (0.00 sec)

Session1>-- Start a Transaction and Insert Data
Session1>BEGIN;
Query OK, 0 rows affected (0.00 sec)

Session1>INSERT INTO t1 VALUES (9999, 3);
Query OK, 1 row affected (0.00 sec)

Session1>INSERT INTO t1 VALUES (9998, 3);
Query OK, 1 row affected (0.00 sec)

Session1>select @@global.read_only, @@read_only;
+--------------------+-------------+
| @@global.read_only | @@read_only |
+--------------------+-------------+
|                  1 |           1 |
+--------------------+-------------+
1 row in set (0.00 sec)

-- Second session set read_only

bin/mysql -uroot -S/tmp/mysql.sock --prompt='SecondSession>'
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 11
Server version: 8.0.41 MySQL Community Server - GPL

Copyright (c) 2000, 2025, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

SecondSession>SET GLOBAL read_only = 1;
Query OK, 0 rows affected (0.01 sec)

SecondSession>SELECT @@global.read_only;
+--------------------+
| @@global.read_only |
+--------------------+
|                  1 |
+--------------------+
1 row in set (0.00 sec)

SecondSession>

-- Commit the transaction in the first session

Session1>COMMIT;
Query OK, 0 rows affected (0.00 sec)

Session1>

.
[31 Jan 7:56] MySQL Verification Team
- Continued

#######. -- Let me repeat once and confirm this

bin/mysql -uroot -S/tmp/mysql.sock
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 7
Server version: 8.0.41 MySQL Community Server - GPL

Copyright (c) 2000, 2025, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> CREATE DATABASE foo;
Query OK, 1 row affected (0.01 sec)

mysql> USE foo;
Database changed
mysql> CREATE TABLE t1 (a INT, b INT) ENGINE=BLACKHOLE;
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE USER 'test_user'@'%';
Query OK, 0 rows affected (0.00 sec)

mysql> GRANT ALL PRIVILEGES ON foo.* TO 'test_user'@'%';
Query OK, 0 rows affected (0.01 sec)

mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)

mysql> \q
Bye

-- connect using 'test_user'@'%' - first session

bin/mysql -utest_user --prompt='Session1>'
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.41 MySQL Community Server - GPL

Copyright (c) 2000, 2025, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

Session1>select @@global.read_only, @@read_only;
+--------------------+-------------+
| @@global.read_only | @@read_only |
+--------------------+-------------+
|                  0 |           0 |
+--------------------+-------------+
1 row in set (0.00 sec)

Session1>use foo
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
Session1>BEGIN;
Query OK, 0 rows affected (0.00 sec)

Session1>INSERT INTO t1 VALUES (9999, 3);
Query OK, 1 row affected (0.00 sec)

Session1>INSERT INTO t1 VALUES (9998, 3);
Query OK, 1 row affected (0.00 sec)

Session1>

-- Second session

 bin/mysql -uroot -S/tmp/mysql.sock --prompt='SecondSession>'
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.41 MySQL Community Server - GPL

Copyright (c) 2000, 2025, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

SecondSession>SET GLOBAL read_only = 1;
Query OK, 0 rows affected (0.00 sec)

SecondSession>SELECT @@global.read_only;
+--------------------+
| @@global.read_only |
+--------------------+
|                  1 |
+--------------------+
1 row in set (0.00 sec)

SecondSession>

-- connect using 'test_user'@'%' - first session and commit but reconnect to see how it behaves

bin/mysql -utest_user --prompt='Session1>'
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.41 MySQL Community Server - GPL

Copyright (c) 2000, 2025, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

Session1>select @@global.read_only, @@read_only;
+--------------------+-------------+
| @@global.read_only | @@read_only |
+--------------------+-------------+
|                  0 |           0 |
+--------------------+-------------+
1 row in set (0.00 sec)

Session1>use foo
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
Session1>BEGIN;
Query OK, 0 rows affected (0.00 sec)

Session1>INSERT INTO t1 VALUES (9999, 3);
Query OK, 1 row affected (0.00 sec)

Session1>INSERT INTO t1 VALUES (9998, 3);
Query OK, 1 row affected (0.00 sec)

Session1>\r
Connection id:    10
Current database: foo

Session1>BEGIN;
Query OK, 0 rows affected (0.00 sec)

Session1>INSERT INTO t1 VALUES (9999, 3);
ERROR 1290 (HY000): The MySQL server is running with the --read-only option so it cannot execute this statement
Session1>INSERT INTO t1 VALUES (9998, 3);
ERROR 1290 (HY000): The MySQL server is running with the --read-only option so it cannot execute this statement
Session1>
===================

Also, I see this : 

###Verify Read-Only Mode is Disabled
```sql
mysql>  select @@global.read_only, @@read_only;
+--------------------+-------------+
| @@global.read_only | @@read_only |
+--------------------+-------------+
|                  1 |           1 |
+--------------------+-------------+
1 row in set (0.00 sec)

^^  Maybe you mistakenly include above in the first session while confirming read_only? I assume it should be listing disabled.

Or am I missing anything here? Pls let me know.

regards,
Umesh
[31 Jan 12:19] Aftab Khan
Thanks for recognising that Blackhole tables are still supported.

The following steps demonstrate that, unlike the InnoDB storage engine, Blackhole tables do not honour the read_only setting for in-flight transactions:

-- Connect using 'test_user'@'%' - First session

mysql(SESSION #1) >  select @@global.read_only, @@read_only;
+--------------------+-------------+
| @@global.read_only | @@read_only |
+--------------------+-------------+
|                  0 |           0 |
+--------------------+-------------+
1 row in set (0.00 sec)

mysql(SESSION #1) > CREATE DATABASE foo;

mysql(SESSION #1) > USE foo;

mysql(SESSION #1) > CREATE TABLE t1 (a INT, b INT) ENGINE=BLACKHOLE;

mysql(SESSION #1) > BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql(SESSION #1) > insert into t1 values (9998,99);
Query OK, 1 row affected (0.00 sec)

# From Session#2 enable read_only

-- connect using 'root'@'%' - Second session

mysql(SESSION #2) > set global read_only=1;
Query OK, 0 rows affected (0.00 sec)

-----------------------------------------------------------------------------------------

#From first session check read_only status (now enabled)

mysql(SESSION #1) >  select @@global.read_only, @@read_only;
+--------------------+-------------+
| @@global.read_only | @@read_only |
+--------------------+-------------+
|                  1 |           1 |
+--------------------+-------------+
1 row in set (0.00 sec)

mysql(SESSION #1) > commit;
Query OK, 0 rows affected (0.00 sec)

# ( This ends in-flight transaction, subsequent inserts will fail e.g.)

mysql(SESSION #1) > insert into t1 values (9998,99);
ERROR 1290 (HY000): The MySQL server is running with the --read-only option so it cannot execute this statement
[31 Jan 12:21] MySQL Verification Team
Thank you for your feedback.
I'll get back to you if anything further needs to investigate this further.

regards,
Umesh
[31 Jan 12:38] Aftab Khan
I have repeated the steps using mysql version 8.40 too, e.g.

 % mysql -u test_user -p -h 127.0.0.1 -P 3306 --prompt='mysql-session-1 >'
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 17
Server version: 8.0.40 Homebrew

Copyright (c) 2000, 2024, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql-session-1 >CREATE DATABASE foo;
Query OK, 1 row affected (0.00 sec)

mysql-session-1 >USE foo;
Database changed
mysql-session-1 >CREATE TABLE t1 (a INT, b INT) ENGINE=BLACKHOLE;
Query OK, 0 rows affected (0.00 sec)

mysql-session-1 >SHOW GRANTS;
+----------------------------------------------------+
| Grants for test_user@%                             |
+----------------------------------------------------+
| GRANT USAGE ON *.* TO `test_user`@`%`              |
| GRANT ALL PRIVILEGES ON `foo`.* TO `test_user`@`%` |
+----------------------------------------------------+
2 rows in set (0.01 sec)

mysql-session-1 >select @@global.read_only, @@read_only;
+--------------------+-------------+
| @@global.read_only | @@read_only |
+--------------------+-------------+
|                  0 |           0 |
+--------------------+-------------+
1 row in set (0.00 sec)

mysql-session-1 >BEGIN;
Query OK, 0 rows affected (0.00 sec)

mysql-session-1 >INSERT INTO t1 VALUES (9999, 3);
Query OK, 1 row affected (0.00 sec)

-------------------------------------------------------------------------------

From second session enable read_only

% mysql -u root -p -h 127.0.0.1 -P 3306 --prompt='mysql-session-2 '
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 19
Server version: 8.0.40 Homebrew

Copyright (c) 2000, 2024, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql-session-2 select @@global.read_only;
+--------------------+
| @@global.read_only |
+--------------------+
|                  0 |
+--------------------+
1 row in set (0.00 sec)

mysql-session-2 set global read_only=1;
Query OK, 0 rows affected (0.00 sec)

mysql-session-2 select @@global.read_only;
+--------------------+
| @@global.read_only |
+--------------------+
|                  1 |
+--------------------+
1 row in set (0.00 sec)

-----------------------------------------------------------------------

Back to session 1

mysql-session-1 >COMMIT;
Query OK, 0 rows affected (0.01 sec)

mysql-session-1 >INSERT INTO t1 VALUES (9999, 3);
ERROR 1290 (HY000): The MySQL server is running with the --read-only option so it cannot execute this statement
mysql-session-1 >
[31 Jan 13:57] MySQL Verification Team
Thank you for the feedback.

regards,
Umesh
[31 Jan 18:32] Perry Harrington
Here is my analysis as part of our internal conversation:

I believe I've found the root cause of why BH doesn't abide by the rules.  The check for read_only is done in 2 places: executing a statement and committing a transaction.  In executing a statement the flag for "raise error if readonly" is set to false, in ha_commit_trans it is set to true.  When committing an Innodb transaction the first check happens and the second check raises the error.  When using blackhole there is only the first check because Blackhole doesn't implement a transaction handler.  The blackhole engine does not implement the commit slot for handlers and thus it ends up be a NULL operation, so commits are not rolled back or blocked when the server is set to readonly.

The fix is to implement the commit handler and put in a check_readonly with an argument of true.  That would prevent blackhole from polluting GTID sets.

To further clarify the last sentence, GTIDs are allocated at commit time, so if a BH transaction is held open when read_only is toggled to 1, when the transaction commits it generates an errant GTID set due to the GTID being allocated *after* read_only is set to 1.  The implications of this are that replication failover for ReplicaSet can result in errant GTIDs on the previous source.

Possible workarounds include killing open transactions (and thus forcing a rollback) when read_only is set to 1, maintaining circular replication for a short period of time (but for how long is only determinable if there are ACTIVE transactions still pending).

But the core issue is that BLACKHOLE violates ACID and it needs to respect read_only and super_read_only.