MySQL Proxy 介於應用與後端資料庫之間,攔截查詢:
關鍵挑戰:MySQL 協議先握手認證,再進入查詢階段。Proxy 必須預先建立並維護對每個後端的已認證連接池,才能在查詢階段無縫切換。
-- read_write_split.lua
-- 非事務 SELECT 路由到空閒 Slave
function read_query(packet)
if proxy.connection.client.is_in_transaction == 0 and
packet:byte() == proxy.COM_QUERY and
packet:sub(2, 7):upper() == "SELECT" then
local max_conns = -1
local chosen = 0
for i = 1, #proxy.servers do
local s = proxy.servers[i]
-- 僅考慮只讀後端且有空閒連線
if s.type == proxy.BACKEND_TYPE_RO and s.idling_connections > 0 then
if max_conns == -1 or s.connected_clients < max_conns then
max_conns = s.connected_clients
chosen = i
end
end
end
if chosen > 0 then
proxy.connection.backend_ndx = chosen
return proxy.PROXY_SEND_QUERY
end
end
-- 預設路由到 Master (寫入或事務中)
return proxy.PROXY_SEND_QUERY
end
| API | 用途 |
|---|---|
| proxy.connection.client.is_in_transaction | 0=非事務,1=事務中 |
| packet:byte() | 封包第一字節,COM_QUERY=3 |
| packet:sub(2, 7) | 取 SQL 前 6 字元判斷 SELECT |
| proxy.servers[i] | 後端伺服器陣列 |
| s.type | BACKEND_TYPE_RW (主) / BACKEND_TYPE_RO (從) |
| s.idling_connections | 空閒連線數 |
| s.connected_clients | 當前使用連線數 |
| proxy.connection.backend_ndx | 指定路由目標後端索引 |
| proxy.PROXY_SEND_QUERY | 繼續發送查詢 |
App --> MySQL Proxy (Lua) --> Master (RW)
--> Slave1 (RO)
--> Slave2 (RO)
mysql-proxy --proxy-address=0.0.0.0:4040 \
--proxy-backend-addresses=10.0.0.1:3306 \ # Master
--proxy-read-only-backend-addresses=10.0.0.2:3306,10.0.0.3:3306 \ # Slaves
--proxy-lua-script=read_write_split.lua \
--admin-username=admin --admin-password=pass \
--admin-lua-script=admin.lua
min_idle_connections 到每個後端。ping 檢測連線存活,失效即時重建。同樣在 read_query 中解析 SQL,提取分片鍵(如 user_id),根據一致性雜湊或範圍映射到對應後端:
local shard_key = extract_shard_key(packet)
proxy.connection.backend_ndx = shard_map[shard_key]
SELECT * FROM proxy.backends; 看連線狀態。proxy.backend_add()、proxy.backend_remove()。is_in_transaction。
4. 大查詢佔據連線池 → 設定 max_query_time 超時熔斷。
5. 準備語句路由 → 需在 prepare_query 階段同樣判斷路由。
MySQL Proxy + Lua 實現讀寫分離雖經典,但新專案建議評估 ProxySQL 等現代化方案。