<p>UPDATE后SELECT查不到新值是因主从复制延迟+读写分离自动路由导致——SELECT被路由至未同步的从库。解决方案包括ShardingSphere的/ sharding hint: write /注释、MyBatis多数据源下的@Master注解、GTID环境下的WAIT_FOR_EXECUTED_GTID_SET等待机制,需按业务敏感度分层使用。</p>

UPDATE 后立刻 SELECT 查不到新值,不是代码写错了,是主从复制延迟+读写分离自动路由共同导致的——你发的 SELECT 被中间件/框架悄悄送到了从库,而那条 UPDATE 还卡在 binlog 传输途中。
为什么 UPDATE 后读不到最新数据
主库执行 UPDATE user SET status = 1 WHERE id = 123 并提交后,该变更需经 binlog 写入 → 网络传输 → 从库 IO 线程拉取 → SQL 线程重放,整个链路存在毫秒到秒级延迟。若此时读请求未做控制,默认走从库,就必然查到旧值(status = 0)或空记录(如涉及 INSERT + SELECT LAST_INSERT_ID() 场景)。
常见错误现象包括:
- 用户下单后立即查订单状态,返回“不存在”
- 后台审核通过后刷新页面,状态仍显示“待审核”
- MyBatis Plus 的
save()+getById()组合在多数据源下返回 null
ShardingSphere-JDBC 中用 SQL Hint 强制走主库
如果你用的是 ShardingSphere-JDBC(非 Proxy),最轻量、无需改代码的方式是在 SQL 前加注释提示:
/* sharding hint: write */ SELECT * FROM user WHERE id = 123;
注意几个硬性细节:
- 注释必须紧贴
SELECT开头,不能换行,不能有空格(SELECT前只能是/*) - 中间件默认只识别
/* sharding hint: write */,不支持/*+ ... */或其他变体 - 该 Hint 对
UPDATE/DELETE无效,仅影响后续SELECT的路由决策 - 若 SQL 被 MyBatis 动态拼接(如
<script>),需确保注释最终出现在语句最前端
MyBatis 多数据源下用 @Master 注解控制
若基于 AbstractRoutingDataSource 自定义了 master/slave 数据源,推荐在 Service 层显式标注读写意图:
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
@Master public User getUserAfterUpdate(Long id) { ... }
关键实现点:
-
@Master是自定义注解,需配合 AOP 切面或 Spring 的TransactionSynchronization在方法入口将线程上下文标记为“需主库” -
determineCurrentLookupKey()方法中检查该标记,返回"master"字符串(而非"slave") - 该注解必须作用于**调用链最外层**的 Service 方法;若嵌套在另一个事务方法内,且外层未标注,则可能被忽略
- 不要和
@Transactional(propagation = Propagation.REQUIRES_NEW)混用——新事务会清空上下文标记,导致路由失效
GTID 环境下用 WAIT_FOR_EXECUTED_GTID_SET 等待同步完成
当 MySQL 已开启 GTID(gtid_mode=ON),且你对一致性要求极高(如金融类确认操作),可让从库主动等待指定事务落地:
SELECT WAIT_FOR_EXECUTED_GTID_SET('b6e8a5a1-1f2d-11ef-8a1a-00155d012345:1-100', 10);
这个函数需在从库上执行,参数分别是 GTID 集合字符串和超时秒数。但要注意:
- 必须先在主库
SELECT @@gtid_executed;获取刚写入的 GTID,再传给从库——不能靠猜或固定值 - 函数返回 0 表示超时未等到,返回 1 表示已应用完毕,此时再
SELECT才安全 - 该方式会阻塞当前连接,高并发场景下容易堆积,不适合普通列表页
- 若从库
read_only=OFF且被误写,GTID 集合可能跳过部分事件,WAIT_FOR_EXECUTED_GTID_SET会永远等下去
真正难处理的不是“怎么强制读主”,而是那些没被标记、又恰好落在延迟窗口里的读请求——它们静默返回旧值,日志里不报错,监控也难捕获。所以别只依赖单一手段,把 Hint、注解、GTID 等方案按业务敏感度分层使用,比全量切主库更可持续。


















