存储过程无法实现自动读写分离,因其不感知主从拓扑,所有sql均在调用连接所在实例执行;call语句本身不触发路由,proxysql等中间件也无法解析过程内部语句,读写分离必须由应用层或中间件在调用前控制连接目标。

存储过程里不能自动路由主从库
SQL 存储过程本身不感知主从拓扑,CALL proc_name() 无论在主库还是从库执行,都只在当前连接的数据库实例上运行。MySQL 不会在执行存储过程时自动把内部的 SELECT 转发到从库、把 INSERT 转发到主库——这是应用层或中间件的责任,不是存储过程的能力范围。
常见误解是“写个存储过程就能读写分离”,实际只要连接指向从库,整个过程(包括写操作)都会失败;若连主库,所有读也全走主库,完全无法分流。
必须由调用方控制连接目标
真正起作用的是调用存储过程时所用的数据库连接归属哪个数据源。策略核心落在外部:谁在调用、连的是哪个库、是否在事务中。
- 如果业务代码通过
AbstractRoutingDataSource或 ShardingSphere-JDBC 路由后拿到的是从库连接,再CALL read_only_proc()才安全;但一旦过程里含UPDATE,就会报错ERROR 1792 (HY000): Cannot execute statement in a READ ONLY transaction(前提是该从库已设read_only=1) - 若过程含写逻辑,调用前必须确保连接来自主库数据源,比如加
@Master注解或显式设置线程上下文DataSourceContextHolder.set("master") - 跨库调用(如主库 CALL 后触发从库 SELECT)在 MySQL 原生存储过程中不可行,不支持分布式事务或跨实例语句转发
替代方案:用函数 + 应用层判断代替复杂存储过程
与其在存储过程中硬编码读写逻辑,不如把判断下推到应用侧,让存储过程保持纯计算或单库操作语义:
- 把“查用户余额+扣款”拆成两步:应用先用
@Slave查余额(走从库),校验通过后再用@Master调用仅含UPDATE的存储过程 - 对纯读场景,可封装为带参数的视图或内联表值函数(
SELECT * FROM user_balance_func(?)),仍由应用决定连哪边 - 避免在存储过程中做
IF ... THEN ... INSERT ... ELSE SELECT ... END IF这类混合操作——它无法被代理层(如 ProxySQL)正确识别路由,容易导致主从不一致或路由失效
ProxySQL 规则对存储过程无效
mysql_query_rules 中的 match_digest 匹配基于原始 SQL 文本,而 CALL my_proc() 是一条固定语句,无法展开识别其内部的 SELECT 或 UPDATE。即使你写了规则:
INSERT INTO mysql_query_rules (rule_id, active, match_digest, destination_hostgroup) VALUES (1, 1, '^CALL', 10);
它只会把所有 CALL 都打到写组(HG=10),无法实现“过程内读走从库”。真正生效的仍是调用连接所属的 hostgroup,不是过程内容。
真正需要读写分离的业务逻辑,别塞进存储过程里——那是路由策略的上游责任区,不是数据库过程该管的事。











