首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

go database sql接口分析及sql埋点实现

!! 大家好,我是蓝胖子,关于sql监控的需求有很多,比如我们常常需要在sql执行前后加上埋点来对sql执行时长,执行语句进行记录,今天我们就来看看在golang中如何实现sql的埋点记录。

首先,我们来看下,在golang中如何实现数据库查询。

golang 实现数据库查询

golang的database/sql包下封装了对数据库查询的接口方法,真正实现数据库连接以及查询的逻辑是由第三方库实现的,拿mysql举例,这个库是github.com/go-sql-driver/mysql。

通常我们执行一个sql语句会先创建一个sql.DB对象,代码如下,

sql.Open方法要求传入驱动名称和数据库连接字符串,数据库驱动是由第三方库注册到全局变量里的,如下:

在执行sql时,一般是通过调用sql.DB对象的Query或者Exec方法,

下面我们就来着重的分析下这两个方法内部是如何执行sql的。

!! 需要注意的是,在分析过程中一定要分清楚哪些是go官方包database/sql 下的逻辑,哪些是第三方库的逻辑,这样可以很好的理解go database/sql包是如何规范sql查询的。

接口分析

db.Query 方法最终会走到db.queryDc 方法,db.Exec最终会走到db.execDc方法,两个方法执行逻辑都是比较类似的,如下所示:

image.png

它们首先都会判断能不能直接执行sql语句,如果不能的话就要使用占位符的方式,prepare和statement的方式来执行sql,返回dirver.ErrSkip错误则代表前者的执行逻辑走不通,需要跳过,接着执行下面的逻辑。

无论是直接执行sql还是使用prepare和statement的方式执行sql,其内部最终都会调用到第三方库封装好的操作数据库的方法。

拿query逻辑,github.com/go-sql-driver/mysql 库举例,database/sql包下用到的连接接口类型实际上是github.com/go-sql-driver/mysql 第三方库返回的连接结构体,所以如果第三方库实现的连接实现类型实现了相应的driver.Queryer或者driver.QueryerContext接口,那么查询时则会走到直接执行sql的方法ctxDriverQuery里。

所以对于第三方库的连接类型实现而言,driver.Queryer接口并不是必需的,database/sql对于连接类型的定义接口只要包含如下几个方法就可以了:

不过github.com/go-sql-driver/mysql 依然为连接类型实现了driver.Queryer接口方法,在这个方法里,由它自己去判断是否能够采取直接执行sql的方式。

直接执行sql相比prepare statement 方式少一次网络传输,效率更高,但会带来sql注入问题。

所以github.com/go-sql-driver/mysql 实现driver.Queryer接口时,判断了 如果写的sql有传参数,比如像这样db.Query("select * from t_user where id = ?;",1),需要将连接配置参数InterpolateParams设置为true才能采取直接执行sql的方式,否则会返回dirver.ErrSkip ,让databse/sql包去执行prepare statement的逻辑。

!! 连接配置参数InterpolateParams设置为true ,github.com/go-sql-driver/mysql 会对sql语句执行转义,避免sql注入。

看到这里,应该明白了golang中执行sql的逻辑,我们的最终目的是对sql执行前后能够加上一些自己的埋点日志。所以接下来,还要看看这部分应该如何来做。

自定义驱动实现sql埋点统计

由于database/sql包下仅仅是定义了一个连接类型的接口,连接类型的真正实现是由第三方库实现的,所以我们可以采取装饰器模式,对github.com/go-sql-driver/mysql  库产生的连接类型进行重新包装,覆盖原有的查询方法,就可以在查询前后加上自己的埋点逻辑了。

连接的创建是由驱动产生的,database/sql包下同样也只定义了一个驱动接口,实现是第三方库实现的。接口如下:

并且database/sql包下还有一个全局变量drivers和一个注册方法,第三方库可以通过这个注册方法把自己实现的驱动注册进去,后续database/sql包下创建连接时,就是通过驱动名创建一个sql.DB 对象,sql.DB对象内部会用对应的驱动实现创建连接。

所以我们要实现自定义的连接类型来包装github.com/go-sql-driver/mysql 库下的连接类型,首先是包装它的驱动类型。如下,我们可以对原有连接和驱动加上钩子函数的属性。

对于新的链接类型只要对它的查询方法进行覆盖就能埋点了,拿query接口举例,在原有的queryContext方法前后加上自己的逻辑。

注意,因为用连接查询的地方涉及到好几个接口方法,我们都需要覆盖到这些方法,才能让埋点不会被漏掉。

这些地方分别是queryDC中执行执行sql时的QueryContext 接口,execDc中执行sql时的ExecContext接口,连接的PrepareContext接口,以及driver.Stmt的ExecContext和QueryContext接口。

完整的实现钩子函数的代码已经上传到github

  • 发表于:
  • 原文链接https://page.om.qq.com/page/OOcjzsqPnmgqj8KVskTJIvzg0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券