MySQL如何允许将READ WRITE
事务设置为READ ONLY
但不能再次返回READ WRITE
?
以下@test
导致SQLException
作为assertThrow
显示和通过。
我已经阅读了https://dev.mysql.com/doc/refman/5.6/en/innodb-performance-ro-txn.html上的手册,其中说明了相同的内容,但这看起来似乎不真实。
将commit
移动到最后避免了Exception
抛出,但我很好奇为什么这是一个问题。虽然与此同时,我试图想到一个边缘情况,这将是一个问题:)
@Test
void testSeparatedCommitBroken() throws SQLException {
try (DataConnection connection = ds.getConnection()) {
connection.setAutoCommit(false);
// first transaction
Map<String, Object> vals1 = new HashMap<>();
vals1.put("name", UUID.randomUUID().toString());
Long id1 = connection.insertRow("products", vals1);
assertNotNull(id1);
connection.commit(); // placing this at the end solves the problem
// check first transaction
Map<String, Object> row1 = connection.selectRow("products", new Pair<>("id", id1));
assertEquals(vals1.get("name"), row1.get("name"));
// second transaction
Map<String, Object> vals2 = new HashMap<>();
vals2.put("name", UUID.randomUUID().toString());
// the connection was set to read only by SELECT which can't be swapped back whilst in autocommit=false
assertThrows(SQLException.class, () -> {
connection.insertRow("products", vals2);
});
connection.setAutoCommit(true);
}
}
常规日志快照
2017-12-31T13:43:37.290875Z 7556 Connect root@localhost on using TCP/IP
2017-12-31T13:43:37.291062Z 7556 Query set autocommit=1
2017-12-31T13:43:37.296937Z 7556 Query SET CHARACTER SET utf8
2017-12-31T13:43:37.297194Z 7556 Query SET NAMES utf8
2017-12-31T13:43:37.298778Z 7556 Query USE `prod_info_mngr`
2017-12-31T13:43:37.298993Z 7556 Query set autocommit=1
2017-12-31T13:43:37.301601Z 7556 Query SET autocommit=0
2017-12-31T13:43:37.303049Z 7556 Query set session transaction read write
2017-12-31T13:43:37.305546Z 7556 Query select @@session.tx_read_only
2017-12-31T13:43:37.315099Z 7556 Query INSERT INTO products (name) VALUES (UUID())
2017-12-31T13:43:37.315814Z 7556 Query commit
2017-12-31T13:43:37.318989Z 7556 Query set session transaction read only
2017-12-31T13:43:37.319842Z 7556 Query select @@session.tx_read_only
2017-12-31T13:43:37.327934Z 7556 Query SELECT * FROM products WHERE id = 162 LIMIT 1
2017-12-31T13:43:37.336647Z 7556 Query set session transaction read write
2017-12-31T13:43:37.337431Z 7556 Query select @@session.tx_read_only
2017-12-31T13:43:37.346483Z 7556 Query INSERT INTO products (name) VALUES (UUID())
SQL自己尝试(这个设计显然是愚蠢的 - 如果你提交每个INSERT,那么为什么要关闭AutoCommit):
CREATE DATABASE `prod_info_mngr` /*!40100 DEFAULT CHARACTER SET utf8 */;
use `prod_info_mngr`;
CREATE TABLE `products` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name_UNIQUE` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
set autocommit=0;
set session transaction read write;
insert into products (`name`) values (UUID());
commit;
set session transaction read only;
select * from products;
set session transaction read write;
insert into products (`name`) values (UUID());
commit;
set autocommit=1;
select * from products;
您无法更改活动事务的特征,您只能对未来事务执行此操作,请参阅SET TRANSACTION
:
您可以在全局,当前会话或下一个事务中设置事务特征:
- [...]
- 使用SESSION关键字,该语句适用于在当前会话中执行的所有后续事务。
因此,在您的情况下,第一个和第二个set session transaction
语句执行您期望的操作:没有活动事务,因此新特性适用于下一个查询。另一方面,第三个set session transaction
语句在事务内部执行,因此在该事务结束之前不会变得相关,因此当您尝试第二次插入时,只读模式仍处于活动状态,从而给出错误。
这也解释了为什么它可以移除第一个commit
:读写模式将在整个事务中保持活动状态,因为set session transaction read only
在事务中执行,因此不会影响它。尝试在所谓的只读部分添加插入。
在设置读写模式之前添加commit
显然也可以解决问题。
试试没有set transaction ...
关键字的session
。它不会做同样的事情(它只适用于下一个事务,而不是所有后续事务),但与使用session
或global
相比,它不允许您在事务中执行它:
在有活跃交易时,不允许没有GLOBAL或SESSION的
SET TRANSACTION
。
所以它会引发你的第三个set transaction
语句本身的错误(不是下面的插入)。它可能会澄清发生了什么以及哪些陈述实际上做了你认为他们做的事情。
虽然允许,但出于某种原因,避免在事务内设置(全局或会话)事务特征通常是个好主意。