登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  数据库 >  MySQL

MySQL 事务只读模式怎样判断当前连接能否写入

来源:17golang原创

时间:2026-10-20 22:49:17 102浏览 收藏

MySQL 里判断“当前连接能不能写入”,不能只执行一次 SELECT @@transaction_read_only 就下结论。这个变量同时涉及全局默认、当前会话默认和下一笔事务三个作用域;如果事务已经启动,还要回看它是怎样启动的。更稳妥的做法是先看连接默认值,再确认事务访问模式,最后把服务器级只读开关作为外层边界。

官方文档:https://dev.mysql.com/doc/refman/8.4/en/set-transaction.html

快速判断可以记成一句话:SESSION.transaction_read_only=0 只说明后续事务默认允许读写,不代表已启动事务一定是读写,也不代表服务器一定接受客户端写入。

先分清 transaction_read_only 的三个观察层级

transaction_read_only 不是一个只会回答“当前 SQL 能不能写”的普通开关。MySQL 文档把事务访问模式拆成全局、会话和下一事务三个层级,最容易误判的地方就是把它们混用。

观察值作用范围能回答什么
@@GLOBAL.transaction_read_only新建会话的默认来源之后新连接默认采用哪种访问模式
@@SESSION.transaction_read_only当前连接的后续事务当前连接默认是 READ ONLY 还是 READ WRITE
@@transaction_read_only下一笔事务只覆盖当前连接即将开始的下一笔事务

例如,SET TRANSACTION READ ONLY 只影响下一笔事务;下一笔结束后,连接会回到原来的 session 访问模式。相反,SET SESSION TRANSACTION READ ONLY 会影响当前连接后续的事务。先把作用域写在排查记录里,后面看到值变化时才不会误认为 MySQL 自己“改回去了”。

MySQL transaction_read_only 全局默认、会话默认和下一事务设置的作用域关系
图1:transaction_read_only 的作用域关系说明图,展示全局、会话和下一事务的边界,不是运行截图。

用 SHOW VARIABLES 判断当前连接的默认访问模式

排查一个连接池连接时,先同时读取全局和 session 值。这样可以看出当前连接是否继承了旧的默认设置,也能避免只看全局值而忽略连接已经建立后的独立状态。

-- 同时查看服务器默认与当前连接默认,避免把两个作用域混为一谈
SELECT
    @@GLOBAL.transaction_read_only  AS global_read_only,
    @@SESSION.transaction_read_only AS session_read_only,
    @@autocommit                    AS autocommit_mode;

-- 读取同一个连接的变量定义,确认客户端没有切换到另一条连接
SHOW SESSION VARIABLES LIKE 'transaction_read_only';

返回 session_read_only = 0 表示当前连接后续事务的默认访问模式是读写,返回 1 表示默认只读。它不是对“已经开启的事务”的追溯查询;如果应用在此之前执行过 START TRANSACTION READ ONLY,仍然要以那次事务的启动方式为准。

另外,连接池复用时不要只在创建连接时检查一次。归还连接前如果有人执行过 SET SESSION TRANSACTION READ ONLY,下一位业务拿到的仍可能是只读默认。更安全的做法是固定借出连接后的初始化语句,或在事务开始前显式声明访问模式。

按启动语句确认当前事务是否只读

正在进行的事务应该优先看启动路径。显式写出访问模式比依赖隐式继承更清楚:START TRANSACTION READ ONLY 明确禁止修改普通表,START TRANSACTION READ WRITE 明确要求读写模式;不写修饰符时才使用默认访问模式。

-- 报表事务明确只读,避免连接默认值被其他调用方改变
START TRANSACTION READ ONLY;
SELECT id, total_amount FROM orders WHERE customer_id = 1001;
COMMIT;

-- 写入事务明确读写,结束后用 COMMIT 或 ROLLBACK 收束状态
START TRANSACTION READ WRITE;
UPDATE orders SET total_amount = total_amount + 10 WHERE id = 1001;
COMMIT;

如果代码使用的是 SET autocommit = 0,事务边界可能由第一条语句隐式开始;这时要把默认值和调用方的事务封装一起看。不要在事务已经开始后才用 SET TRANSACTION READ ONLY 推断或修改当前事务,下一事务语法本身就不允许在事务进行中设置。

一个实用的判断顺序是:事务是否已结束、启动语句是否包含 READ ONLY、没有显式声明时 session 默认是什么。只要启动路径不明确,就不要把 @@SESSION.transaction_read_only 当作当前事务的完整证据。

必要时从 INNODB_TRX 观察活动事务

在 InnoDB 事务正在执行且具备相应查看权限时,可以从 INFORMATION_SCHEMA.INNODB_TRX 读取 TRX_IS_READ_ONLY。它适合观察服务端当前能看到的活动事务,但不应当被当成应用审计日志:只读且非锁定的短事务可能不会生成可长期查询的事务记录,状态本身也会快速变化。

-- 用当前连接的线程标识关联活动事务,减少误读其他连接的结果
SELECT
    trx_id,
    trx_started,
    trx_state,
    trx_is_read_only,
    trx_autocommit_non_locking
FROM information_schema.innodb_trx
WHERE trx_mysql_thread_id = CONNECTION_ID();

看到 trx_is_read_only = 1,说明这条可见的 InnoDB 活动事务被标记为只读;看到 0 则表示它不是只读事务。没有返回行不等于“当前连接一定可写”,也可能是事务很短、尚未被 InnoDB 记录,或者当前语句没有形成可见的活动事务。读取该表还需要相应的 PROCESS 权限,因此应用层通常仍应保留自己的事务启动记录。

把 transaction_read_only 与服务器只读开关分开

事务访问模式允许当前事务读写,并不等于服务器一定接受客户端写入。服务器级 read_only 和 super_read_only 是另一层限制;排查时至少把它们和事务值一起记录。本文不展开权限或复制拓扑,但这两个值足以解释很多“连接看起来是读写,UPDATE 仍被拒绝”的现象。

-- 记录事务层与服务器层的只读状态,避免只凭一个变量下结论
SELECT
    @@SESSION.transaction_read_only AS session_tx_read_only,
    @@GLOBAL.read_only              AS server_read_only,
    @@GLOBAL.super_read_only        AS server_super_read_only;

-- 明确开启读写事务;如果服务器策略不允许,错误信息应作为下一条线索
START TRANSACTION READ WRITE;

还要记住两个边界:只读事务仍可以修改临时表,但不能把这个例外当成普通业务表可写;服务器只读开关的实际效果还会受到账号能力和当前部署策略影响。因此“当前连接能否写入”最好拆成两个问题:事务访问模式是否允许,以及服务器策略是否接受。

MySQL 事务访问模式、INNODB_TRX 和 read_only 服务器开关的静态判断关系
图2:MySQL 事务写入判断的边界关系图,连接事务状态与服务器只读开关,不代表某次真实执行结果。

在应用连接池中固定检查与回滚策略

连接池场景最容易出现状态串线:A 请求把连接改成只读,连接归还后,B 请求继承了这个 session 设置。解决办法不是每次写入前盲目重试,而是把事务模式当成连接状态管理的一部分。

-- 借出连接后恢复一个明确的后续事务默认值
SET SESSION TRANSACTION READ WRITE;

-- 只读任务单独限定下一笔事务,结束后不污染后续事务
SET TRANSACTION READ ONLY;
START TRANSACTION;
SELECT id, status FROM orders WHERE id = 1001;
COMMIT;

-- 业务异常时显式回滚,避免连接带着未收束事务回到连接池
ROLLBACK;

如果使用驱动或 ORM 的事务 API,仍要确认它最终发送的事务启动语句和连接复用策略。生产排查记录建议保留连接标识、session 值、事务启动方式、TRX_IS_READ_ONLY(若可见)以及服务器只读值;这样下次遇到写入失败时,可以直接定位是作用域继承还是服务器边界。

速查:当前连接能否写入怎样判断

  1. 先查 @@SESSION.transaction_read_only,确认当前连接后续事务默认模式。
  2. 再看事务是如何启动的:START TRANSACTION READ ONLY、READ WRITE 还是继承默认。
  3. 事务正在运行且需要服务端证据时,尝试读取 INNODB_TRX.TRX_IS_READ_ONLY,没有记录不能直接当成可写。
  4. 同时记录 @@GLOBAL.read_only 和 @@GLOBAL.super_read_only,把服务器级限制与事务级限制分开。
  5. 连接池归还前结束事务,借出后恢复明确默认值,减少只读状态跨请求传播。

相关问题

@@SESSION.transaction_read_only = 0 就代表当前事务能写吗?

不一定。它只表示当前连接后续事务的默认访问模式;已经启动的事务可能是显式 READ ONLY,服务器级只读开关也可能阻止客户端写入。

SET TRANSACTION READ ONLY 会永久改变连接吗?

不会。无 SESSION 或 GLOBAL 关键字时,它只作用于当前连接的下一笔事务;如果要影响该连接后续所有事务,应明确使用 SESSION 作用域。

为什么 INNODB_TRX 查不到自己的事务?

事务信息是快速变化的,非锁定只读事务可能不会建立可长期观察的事务记录,且查询还需要相应权限。应用仍应记录自己的事务启动方式。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>