进程间传递 SQL 文的方法

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 文还可以进一步适配具体项目,做调整

具体就不展开说明了

相关推荐
KELLENSHAW37 分钟前
MySQL45讲 第三十七讲 什么时候会使用内部临时表?——阅读总结
数据库·mysql
一只淡水鱼6642 分钟前
【mybatis】详解 # 和 $ 的区别,两者分别适用于哪种场景,使用 $ 不当会造成什么影响
sql·spring·mybatis·sql注入
zhangphil43 分钟前
Android adb查看某个进程的总线程数
android·adb
小刘鸭!2 小时前
Hbase的特点、特性
大数据·数据库·hbase
凡人的AI工具箱2 小时前
每天40分玩转Django:Django表单集
开发语言·数据库·后端·python·缓存·django
奔跑草-2 小时前
【数据库】SQL应该如何针对数据倾斜问题进行优化
数据库·后端·sql·ubuntu
Elastic 中国社区官方博客2 小时前
如何通过 Kafka 将数据导入 Elasticsearch
大数据·数据库·分布式·elasticsearch·搜索引擎·kafka·全文检索
P.H. Infinity2 小时前
【Redis】配置序列化器
数据库·redis·缓存
猛男技术控3 小时前
Milvus矢量数据库 麒麟v10安装
数据库·milvus
别致的影分身3 小时前
MySQL 常用程序介绍
数据库·mysql