存储过程内硬编码读写操作会破坏读写分离,导致读请求全部压向主库;应将读逻辑移至应用层,由其选择读库或写库连接,存储过程仅处理写操作与强一致性校验。

存储过程中硬编码读写连接会破坏负载均衡
SQL Server 或 MySQL 存储过程里直接 SELECT 或 UPDATE,默认走的是当前连接的物理节点——如果应用层做了读写分离(比如用 ProxySQL、MaxScale 或 ShardingSphere),但存储过程内部没区分,所有语句都发到主库,读压力根本分不走。
常见错误现象:SHOW PROCESSLIST 看到大量 SELECT 出现在主库;监控显示从库 CPU 长期低于 5%,主库却持续过载。
- MySQL 存储过程无法动态切换连接,
USE db_name不等于换实例,只是切库名 - SQL Server 的
EXEC AT linked_server能跨实例,但权限配置复杂、网络延迟高,不适合高频读 - 真正可行的做法是:把读逻辑拆出存储过程,由应用层决定走读库还是写库;存储过程只做写+强一致性校验
用注释提示读写意图,配合中间件识别
某些中间件(如 MyCat、ShardingSphere-JDBC)支持通过 SQL 注释识别读写意图,比如 /*#mycat:sql=select*/。但注意:存储过程里的注释不会被中间件解析——因为整个 CALL proc_name() 被当成一条语句转发了。
所以必须在调用侧处理:
- 应用代码中,对纯读场景,显式用读库连接执行
SELECT,再用写库连接执行CALL proc_update_order() - 避免在存储过程中嵌套
SELECT ... INTO @var后再UPDATE,这种“先读后写”逻辑会让中间件误判为写请求,强行发到主库 - 如果非得在过程内查数据,用
SELECT ... FOR UPDATE显式声明需要锁,否则有些中间件会按“普通 SELECT”路由到从库,导致幻读
SQL Server 中使用 READONLY 提示可能被忽略
SQL Server 允许在查询末尾加 OPTION (RECOMPILE, READONLY),但这是优化器提示,不是路由指令。它不影响连接去向,只影响执行计划是否复用、是否允许修改数据。
实际效果很有限:
- 即使写了
READONLY,只要连接本身连的是主库,语句照样跑在主上 - Always On 可读副本需在连接字符串里指定
ApplicationIntent=ReadOnly,这个参数必须由客户端驱动传入,存储过程无法覆盖 - 验证方式:在从库上执行
SELECT @@SERVERNAME,如果返回主库名,说明没走过去
MySQL 存储过程里调用 SELECT SLEEP() 会阻塞主库事务
有人用 SLEEP() 做延时重试或限流,但它在存储过程中会持住事务锁,且因主从复制是单线程回放(尤其 MySQL 5.7 默认),会导致从库同步延迟飙升。
更隐蔽的问题是:SLEEP() 在存储过程中属于“不可中断等待”,一旦卡住,只能 kill 连接,不能靠超时自动释放。
- 替代方案:把延时逻辑移到应用层,用
Thread.sleep()或setTimeout()控制,让数据库只做确定性操作 - 如果必须在服务端控制节奏,改用
SELECT COUNT(*) FROM information_schema.columns LIMIT 1这类轻量查询代替SLEEP(),至少能被KILL中断 - 任何涉及时间等待的存储过程,上线前必须在从库延迟监控面板上实测 10 分钟以上
SELECT,结果整条链都被中间件判定为写流量。这时候光看单个过程体没用,得顺着 CALL 层级逐层检查读写混合点。

















