我们主要来分析一下查询的实现,涉及的是database/sql
和go-sql-driver/mysql
的部分源码。database/sql
是go对于db抽象出的一个标准库,go-sql-driver/mysql
是实现了database/sql
驱动接口的mysql驱动。
什么是数据库驱动?
简单来讲,数据库驱动实现了mysql协议,比如连接数据库,驱动会给数据库服务器发送
握手初始化报文
和登陆认证报文
,拼出报文头,将username,password放在报文的固定位置,将[]byte数据写入到socket,这就是驱动的主要功能,他给我们(这里指database/sql)完成了底层的操作
查询的接口主要有两个:
|
|
再来看看返回值的结构:
|
|
可以明显地看到Row将Rows包了一层,加了一个err字段,上面注释中说的而它引起的错误则会被推延到数据行的 Scan 方法被调用为止也就可以理解了,错误在error上而没有直接返回。
个人认为看源码最好带着问题看,有目的往往更能坚持。我们先来看一段最基本的调用代码,看看能不能找出几个我们感兴趣的问题:
|
|
我的问题来了:
这里query用的sql格式看上去是
prepared statement
,我们知道prepared statement
是可以防注入的。我们也知道不能够直接拼接字符串,直接拼接定会引入注入问题。那么问题来了,prepared statement
会增加与服务器的交互,影响性能,是否能不使用prepared statement
,如果不使用,像上面这样的query能否防注入呢?另外上面我们也只是说了看上去是
,底层是否真的在使用prepared statement
?要想使用
prepared statement
在数据库提前编译,复用的特性需要在客户端做很多事,比如JDBC就实现了一整套方案。目前看来database/sql没有这样的实现,所以这里可以忽略编译的优势。这个for的写法真是奇怪,简直看不懂到底在遍历什么,每次都是
rows.Scan
,rows
里面到底存了些什么可以这么玩
下面我们从这些问题出发来研究一下实现,我们会忽略掉与问题无关的部分(比如连接池),只关注我们需要的链路。
Prepared Statement & 防注入
Query之后还有几个函数调用才能到下面的函数,包括了取连接和错误处理的逻辑,这些对我们的问题没有影响,暂且忽略它们。
|
|
从上面的注释可以看到,Query默认不使用prepared statement
,但是要保证正常查询不返回driver.ErrSkip
,一旦返回这个错误,则会使用prepared statement
继续查询。再往下跟我们将会走到driver中,我们来看看真实的query,在go-sql-driver/mysql
的connection.go
。
|
|
InterpolateParams从字面上看意思是是否可以插值,它其实是dsn的一个参数,见dsn.go
|
|
这里我们就清楚了,如果不想使用prepared statement
,就要在dsn
中加入interpolateParams=true
,允许驱动进行对sql进行插值。下一个问题,这里的插值能不能防注入?我们来看interpolateParams
方法,我们只看参数为string的情况:
|
|
从函数名我相信大家已经知道了,我们将会对字符串参数进行转义,而转义特殊字符是可以防止注入的,不妨再看看escapeStringBackslash
:
|
|
一目了然。上面的分析基本解决了我们的第一个问题:dsn中加入interpolateParams=true
可以不使用prepared statement
,将sql的参数传入Query方法可以防止注入,防注入是驱动通过转义特殊字符来实现的。
第二个问题我们可以接着第一个问题的代码往下看,上面我们看了拼接好sql,下面就是真正的查询了,代码在statement.go
|
|
看到了吗,query最后返回的时候,只是把字段名读出来了而已,根本没有读到我们需要的数据库中的记录。不妨大胆猜测:for Next将会每次从buffer中读出一条记录,将其赋值给某个变量,Scan就是在解析这个变量。让我们回到database/sql
,在sql.go中:
|
|
再回到go-sql-driver/mysql
|
|
来来回回啊,再看database/sql
的sql.go
|
|
至此我们的第二个问题也解决了,Query返回的时候只是取出了字段信息,真实的数据库记录还留在buffer中,for循环Next,每次从buffer中读取一条记录,存在rows结构体的lastcols字段中,调用Scan的时候就是从lastcols取出值。
上面只是对大致流程的分析,里面还有大量的细节没有涉及到,特别是对于协议的实现,非常值得一看。
本文如有错误,欢迎联系O(∩_∩)O