SQL 文組成
SQL 文有 2 部分組成:
- SQL 原型,如:
INSERT INTO `test1` (`id`,`name`) VALUES (?,?)
- Args ,? 號對應的值列表
有時,生成 SQL 文的進程和處理 SQL 文的進程,可能不是同一個
這里就涉及到如何高效的把 SQL 文從一個進程傳遞給另外一個進程
EscapeSQL
MySQL 原生 C Api 叫mysql_real_escape_string
該方法可以把 SQL 文兩部分構成轉義為一條 SQL 字符串
因為是字符串 SQL 語句,因此可以方便的從一個進程傳遞到另外一個進程
但是這種方法,有 2 個缺點:
- 流量變大, Args 是需要轉義的,會插入很多很多的
\
- 無法使用 MySQL 的預處理(
PREPARE STATEMENT
),使用 MySQL 的性能會變差
Gorm 庫中,無 EscapeSQL API
項目用的是 Gorm
為了調通流程,首先考慮的是通過EscapeSQL
傳遞 SQL 字符串
令人驚訝的是,Gorm 庫并無EscapeSQL
相關的 API
考慮到,Gorm 庫也是使用 go-sql-driver/mysql 庫和 MySQL 交互
因此查看 go-sql-driver/mysql 庫
結果,go-sql-driver/mysql 庫也無EscapeSQL
相關的 API
GitHub 上搜了下 Issues ,發現 golang 庫也有類似的提問、討論: https://github.com/golang/go/issues/18478
說明,EscapeSQL 本身是不建議被使用的
go-sql-driver/mysql 庫 SQL 文處理流程
單步調試了下 go-sql-driver/mysql 的簡易例子
發現內部自動以下流程:
- 判斷 Args 是是否長度大于 0
- 否,直接執行 SQL 文
- 是,
PREPARE STATEMENT
預處理 SQL 原型- 執行預處理,傳遞 Args
借鑒 go-sql-driver/mysql 庫的思路,可以得出以下結論:
- 進程間傳遞 SQL 文也可以分 2 個步驟:
- 傳遞 SQL 原型
- 傳遞 Args
因此問題轉化為如何高效的傳遞 Args
進程間傳遞 Args
通常進程間傳遞數據,可以定義協議。這樣另一個進程可以根據協議理解數據是什么
Args 值列表,存在任意組合性
因此,協議的定義需要一點技巧
粗糙的思路類似:
值部分 | 說明 |
---|---|
type | 指明數據類型 |
data | 具體數據 |
這樣傳遞給另一個進程也能理解
protobuf 的 protowire 包
google.golang.org/protobuf/encoding/protowire protobuf 的編碼器
因為項目中使用 protobuf ,那么可以使用它,減少重復構造輪子
protowire 庫序列化方法,類似:
var b []byteb = protowire.AppendTag(b, protowire.Number(index), protowire.VarintType)
b = protowire.AppendVarint(b, uint64(val))b = protowire.AppendTag(b, protowire.Number(index), protowire.Fixed32Type)
vv := math.Float32bits(val)
b = protowire.AppendFixed32(b, vv)b = protowire.AppendTag(b, protowire.Number(index), protowire.Fixed64Type)
vv := math.Float64bits(val)
b = protowire.AppendFixed64(b, vv)b = protowire.AppendTag(b, protowire.Number(index), protowire.BytesType)
b = protowire.AppendString(b, val)
protowire 庫反序列化方法,類似:
// b data
var vals []interface{}
_, t, i := protowire.ConsumeTag(b)
switch t {
case protowire.VarintType: v, n := protowire.ConsumeVarint(b)b = b[n:]vals = append(vals, v)
case protowire.BytesType: v, n := protowire.ConsumeString(b)b = b[n:]vals = append(vals, v)
case protowire.Fixed32Type:v, n := protowire.ConsumeFixed32(b)b = b[n:]vals = append(vals, math.Float32frombits(v))
case protowire.Fixed64Type: v, n := protowire.ConsumeFixed64(b)b = b[n:]vals = append(vals, math.Float64frombits(v))
}
方法匯總
2 個進程間傳遞 SQL 文
方法 | 建議 |
---|---|
使用 EscapeSQL | 不推薦 |
SQL 原型 + 自定義 Args 序列化規則 | 看項目情況 |
SQL 原型 + Args Protobuf 序列化規則 | 推薦 |
擴展
因為每個項目的存儲方式會有差異。這樣 2 個進程間傳遞 SQL 文還可以進一步適配具體項目,做調整
具體就不展開說明了