Skip to content

Commit 8194409

Browse files
tianxjtolmer
authored andcommitted
Per Database Read-Only
Summary: Implements per-database scope read-only setting. The read-only can be turned on in `alter database` statement. Privilege to set read-only follows alter-database ACL. ALTER DATABASE db_name [SUPER_READ_ONLY | READ_ONLY = FALSE | TRUE]] `SUPER_READ_ONLY = TRUE` will prevent all write transactions from committing for any user (including super users). `READ_ONLY = TRUE` will prevent any write transaction from committing for regular users, while super users can still write to the database. `SUPER_READ_ONLY = FALSE` will turn off super_read_only on the database, and the database remains on read_only (for regular users), `READ_ONLY = FALSE` will turn off read_only on the database, The READ_ONLY database status can be shown by `show create database`. mysql> alter database test read_only = true; Query OK, 1 row affected (0.00 sec) mysql> show create database test; +----------+---------------------------------------------------------------------------+ | Database | Create Database | +----------+---------------------------------------------------------------------------+ | test | CREATE DATABASE `test` /*!40100 DEFAULT CHARACTER SET latin1 READ_ONLY */ | +----------+---------------------------------------------------------------------------+ 1 row in set (0.00 sec) mysql> alter database test super_read_only = true; Query OK, 1 row affected (0.00 sec) mysql> show create database test; +----------+---------------------------------------------------------------------------------+ | Database | Create Database | +----------+---------------------------------------------------------------------------------+ | test | CREATE DATABASE `test` /*!40100 DEFAULT CHARACTER SET latin1 SUPER_READ_ONLY */ | +----------+---------------------------------------------------------------------------------+ 1 row in set (0.00 sec) mysql> alter database test super_read_only = false; Query OK, 1 row affected (0.00 sec) mysql> show create database test; +----------+---------------------------------------------------------------------------+ | Database | Create Database | +----------+---------------------------------------------------------------------------+ | test | CREATE DATABASE `test` /*!40100 DEFAULT CHARACTER SET latin1 READ_ONLY */ | +----------+---------------------------------------------------------------------------+ 1 row in set (0.00 sec) Details: * READ_ONLY flag is persisted in db.ops and survives restarts. * Alter database will only return when no write transaction is still committing. Uncommitted write transactions will fail after alter database succeeds. * DB options are stored in each thread (using hash map), so checking the read_only options doesn't grab a global lock. Only db_read_only that is turned on is stored in the local hash map. * The first time thead local hash map is initialized (once), shared db option map will be locked and accessed. Since the db options are not automatically loaded into the shared hash map (cache) at the beginning, we have to explicitly load the db options during the first time any thread initializes its local hash map. * create/alter/delete database DDLs will iterate through thread array to update local db opt (if needed), similar to how show processlist works. * write transactions are checked against local hashmap at commit time (either explicit commit or auto-commit). This will make the explicit commit and auto-commits consistent, and avoid problems such that explicit commit may become a "read-only" transaction if we block write-statements at the parsing time. * The list of databases accessed in a write transaction is obtained through the metadata locks (MDL) that the transaction holds. As I noted in the code, this may rollback some false positives, such as the read-only DB in the list may not actually be modified in the transaction (for cross-db transactions). We do not differentiate such cases. Test Plan: main.db_read_only Reviewers: ebergen, santoshb, pengt Reviewed By: pengt
1 parent 7367a3e commit 8194409

27 files changed

+1631
-18
lines changed

Diff for: mysql-test/include/db_read_only_off.inc

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
truncate t1;
2+
truncate t2;
3+
4+
begin;
5+
insert into t1 values (0), (1), (2);
6+
insert into t2 values (0), (1), (2);
7+
update t1 set a = a + 1;
8+
# multiple table updates
9+
update t1, t2 set t1.a=t1.a+1, t2.a=t2.a*2;
10+
select 't1', a from t1;
11+
select 't2', a from t2;
12+
commit;
13+
14+
alter table t1 add key (a);
15+
describe t1;
16+
drop index a on t1;
17+
18+
alter table t2 add key (a);
19+
describe t2;
20+
drop index a on t2;
21+
22+
truncate t1;
23+
truncate t2;
24+
25+
--echo # populate some data
26+
begin;
27+
insert into t1 values (1), (2), (3);
28+
insert into t2 values (1), (2), (3);
29+
select 't1', a from t1;
30+
select 't2', a from t2;
31+
commit;

Diff for: mysql-test/include/db_read_only_on.inc

+120
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
--echo # continue previous transaction
2+
--echo # read_only was turned on in the middle of a transaction
3+
--echo # new update/insert statement will be blocked immediately
4+
update t1 set a = a + 1;
5+
# multiple table updates
6+
update t1, t2 set t1.a=t1.a+1, t2.a=t2.a*2;
7+
select 't1', a from t1;
8+
select 't2', a from t2;
9+
10+
--echo # write transaction was rolled back at the end
11+
--error ER_DB_READ_ONLY
12+
commit;
13+
14+
# insert/update rolled back
15+
select 't1', a from t1;
16+
select 't2', a from t2;
17+
18+
--echo # write transaction with 'begin'
19+
begin;
20+
insert into t1 values (4), (5), (6);
21+
update t1 set a = a + 1;
22+
# creating a table DDL is failed immdiately
23+
--error ER_DB_READ_ONLY
24+
create table t3 (a int) engine=innodb;
25+
select a from t1;
26+
--error ER_DB_READ_ONLY
27+
commit;
28+
29+
--echo # read-only transactions are ok
30+
begin;
31+
select count(*) from t1;
32+
select count(*) from t2;
33+
commit;
34+
35+
--echo # transaction without 'begin'
36+
insert into t1 values (4), (5), (6);
37+
insert into t1 values (7), (8), (9);
38+
select a from t1;
39+
--error ER_DB_READ_ONLY
40+
commit;
41+
select a from t1;
42+
43+
--echo # rolled-back transaction
44+
insert into t1 values (4), (5), (6);
45+
insert into t1 values (7), (8), (9);
46+
select a from t1;
47+
rollback;
48+
select a from t1;
49+
50+
set autocommit = 1;
51+
--echo # multiple table updates (autocommit)
52+
--error ER_DB_READ_ONLY
53+
update t1, t2 set t1.a=t1.a+1, t2.a=t2.a*2;
54+
select 't1', a from t1;
55+
select 't2', a from t2;
56+
set autocommit = 0;
57+
58+
# table update DDL is blocked
59+
--error ER_DB_READ_ONLY
60+
alter table t1 add key (a);
61+
62+
# table update DDL is blocked
63+
--error ER_DB_READ_ONLY
64+
create index a on t1 (a);
65+
66+
# drop table is not allowed
67+
--error ER_DB_READ_ONLY
68+
drop table t1;
69+
70+
# drop database is not allowed
71+
--error ER_DB_READ_ONLY
72+
drop database test;
73+
74+
--echo #
75+
--echo # OK to create temporary table
76+
--echo #
77+
create temporary table temp1 (a int);
78+
insert into temp1 select * from t1;
79+
update temp1 set a = a + 1;
80+
select * from temp1;
81+
drop temporary table temp1;
82+
83+
--echo #
84+
--echo # OK to switch and write another database
85+
--echo # read_only scope is per database
86+
--echo #
87+
create database test2;
88+
use test2;
89+
show create database test2;
90+
create table t1 (a int) engine = innodb;
91+
insert into t1 values (0), (1), (2);
92+
update t1 set a = a + 1;
93+
select a from t1;
94+
95+
--echo #
96+
--echo # cross-db/noncurrent-db transaction
97+
--echo # Transaction writing to test db from session of test2 db
98+
--echo #
99+
begin;
100+
insert into test.t1 values (4), (5), (6);
101+
update test.t1 set a = a + 1;
102+
select a from test.t1;
103+
--error ER_DB_READ_ONLY
104+
commit;
105+
select a from test.t1;
106+
select a from test2.t1;
107+
108+
begin;
109+
insert into test.t1 values (4), (5), (6);
110+
update test.t1 set a = a + 1;
111+
select a from test.t1;
112+
update test2.t1 set a = a + 1;
113+
select a from test2.t1;
114+
--error ER_DB_READ_ONLY
115+
commit;
116+
select a from test.t1;
117+
select a from test2.t1;
118+
119+
use test;
120+
drop database test2;

0 commit comments

Comments
 (0)