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 库序列化方法,类似:
go
var b []byte
b = 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 库反序列化方法,类似:
go
// 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 文还可以进一步适配具体项目,做调整
具体就不展开说明了