【问题标题】:MySQL cannot execute statement in READ ONLYMySQL 无法以只读方式执行语句
【发布时间】:2017-12-31 13:36:14
【问题描述】:

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;

【问题讨论】:

  • 我不关注您问题的前半部分,但您在这里遇到异常并不奇怪。您正在提交事务,然后尝试在同一事务上再次插入。
  • @TimBiegeleisen 为了清楚起见,我将添加登录。而且你可以在同一个事务上多次提交。
  • 我想我刚刚学到了一些新东西 :-)
  • @TimBiegeleisen 在过去的 15 年中每天都学到新东西!以这个问题为例。虽然通过更好的设计很容易避免。
  • 我已经从您的日志中测试了 SQL,它适用于 MySQL 5.7.20

标签: mysql


【解决方案1】:

您无法更改活动交易的特征,您只能为将来的交易这样做,请参阅SET TRANSACTION

您可以为当前会话或下一个事务全局设置事务特征:

  • [...]
  • 使用 SESSION 关键字,该语句适用于在当前会话中执行的所有后续事务

因此,在您的情况下,第一个和第二个 set session transaction 语句执行您的预期:没有活动事务,因此新特征适用于下一个查询。另一方面,第三个set session transaction 语句在事务中执行,因此在该事务结束之前不会变得相关,因此当您尝试第二次插入时,只读模式仍然处于活动状态,从而给您错误。

这也解释了为什么如果你删除第一个commit 它会起作用:读写模式将在整个事务中保持活动状态,因为set session transaction read only 在事务内部执行,因此不会影响它。尝试在应该是只读的部分中添加插入。

在设置读写模式之前添加commit显然也可以解决问题。

试试不带session 关键字的set transaction ...。它不会做同样的事情(它只适用于下一个事务,而不是所有后续事务),但与使用sessionglobal 相比,它不允许您在事务中执行它:

SET TRANSACTION 在有活动事务时不允许不带 GLOBAL 或 SESSION。

所以它会为您的第三个set transaction-statement 本身(而不是下面的插入)引发错误。它可能会澄清正在发生的事情以及哪些语句实际上做了你认为的事情。

虽然允许,但出于此类原因,通常最好避免在事务中设置(全局或会话)事务特征。

【讨论】:

    猜你喜欢
    • 2019-07-04
    • 1970-01-01
    • 2021-12-14
    • 1970-01-01
    • 2019-11-28
    • 2013-06-28
    • 2016-03-28
    • 2014-01-01
    • 1970-01-01
    相关资源
    最近更新 更多