在构建高并发Web应用时,MySQL常成为性能瓶颈。将写操作指向主库、读操作分散到多个从库,能有效减轻单点压力。除了使用MyCat、ProxySQL等专用数据库中间件,也可以基于Nginx的stream模块在TCP层完成MySQL协议的转发,从而实现轻量级的读写分离代理。这种方式不需要额外维护复杂的中间件进程,适合中小规模或边缘业务使用。

基于Nginx stream模块的基础代理架构
Nginx从1.9.0版本开始引入了stream模块,使得Nginx能够处理TCP和UDP流量,而不仅仅是HTTP。对于MySQL来说,客户端与服务器之间是通过TCP承载的私有二进制协议通信,Nginx可以在不了解协议细节的情况下,单纯做四层转发。我们需要定义两个上游组,一个代表主库(处理写和强一致读),一个代表从库集群(处理弱一致读)。
在配置中,使用upstream指令声明主从节点,并通过stream块监听3306端口。由于Nginx本身不解析MySQL命令,最基础的读写分离思路是:为写和读分配不同端口,例如3306走主库,3307走从库,由应用层在连接时自行选择。虽然这把路由逻辑推给了业务代码,但架构清晰且不易出错。如下是一个简化的配置示例:
stream {
upstream mysql_master {
server 192.168.0.1:3306 weight=1;
}
upstream mysql_slave {
server 192.168.0.2:3306 weight=1;
server 192.168.0.3:3306 weight=1;
}
server {
listen 3306;
proxy_pass mysql_master;
}
server {
listen 3307;
proxy_pass mysql_slave;
}
}
这种双端口方案的优势在于稳定可靠,不会因为SQL解析错误导致路由异常。缺点也很明显:应用必须修改连接逻辑,且难以在事务中动态切换。如果希望在单一端口上自动分离,就需要引入更进一步的判断机制,例如利用Nginx的Lua脚本或外部认证代理,但那已经超出纯配置范畴。
利用连接参数与SQL嗅探做智能路由
要在单一监听端口内区分读写,一种可行思路是在连接建立阶段注入路由信息。例如约定客户端连接时携带特定用户名后缀,如app_write走主库,app_read走从库。Nginx的stream模块配合map指令,可以根据连接握手包中的用户名进行分发。不过MySQL握手为二进制协议,原生Nginx无法直接读取,通常需要借助nginx-stream-lua-module编写Lua代码解析前几个字节。
另一种偏运维的思路是SQL嗅探:在Nginx转发前,由Lua协程读取客户端发来的第一个请求包,判断是COM_QUERY且以SELECT开头,则转发到从库;若包含INSERT、UPDATE、DELETE则转发主库。示例Lua片段逻辑如下,注意实际需处理包长和序列号:
local sock = ngx.socket.tcp()
local first_pkt = sock:receive(5)
-- 简化判断:若首字节为查询命令且后续含SELECT则从库
if string.find(first_pkt, 'SELECT') then
ngx.var.backend = 'mysql_slave'
else
ngx.var.backend = 'mysql_master'
end
这种嗅探方式对简单业务有效,但面对预处理语句、事务开启后的读请求,极易误判。比如事务内执行SELECT ... FOR UPDATE必须走主库,而嗅探可能误投从库造成死锁或数据偏差。因此生产环境若采用Nginx代理,建议仅将其作为入口分流,复杂路由仍交由应用或专业中间件。
相较专用中间件的优劣与适用边界
使用Nginx做MySQL代理最直观的好处是复用现有网关,节省部署成本。Nginx内存占用极低,在边缘节点或内部测试环境能快速搭建读写分离演示。同时它的健康检查机制(如proxy_next_upstream的TCP等价指令)可自动剔除宕机从库,提升可用性。
然而必须认清其不足:Nginx不原生理解MySQL协议,无法做基于表级别、基于注释hint的精细控制,也不能像ProxySQL那样缓存查询结果。当从库延迟较大时,Nginx无法感知复制滞后,可能把读请求发给严重落后的节点。这就要求运维侧配合监控脚本,动态从上游组移除延迟超阈值的实例。
综合来看,如果业务读多写少、容忍秒级从库延迟、且团队已熟练使用Nginx,那么采用Nginx+多端口或轻量Lua路由是合理选择。若系统对数据一致性要求极高、存在大量跨事务混合读写,则应当选用支持协议级解析的数据库代理。明确思路与边界,才能把Nginx的价值发挥在合适的地方。
NginxMySQLread_write_splitting修改时间:2026-08-14 14:09:26