dive into golang database/sql(1)

数据库操作是一个应用必不可少的部分,但是我们很多时候对golang的sql包仅仅是会用,这是不够的。每一条语句的执行,它的背后到底发生了什么。各式各样对sql包的封装,是不是有必要的,有没有做无用功?

这是go to database package系列文章的第一篇。本系列将按照程序中使用sql包的顺序来展开

先来看一段简短的代码:

package main
import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
    "fmt"
)

func main() {
    db, err := sql.Open("mysql", "user:password@/dbname")
    if nil != err {
        panic(err)
    }
    age := 18
    rows,err := db.Query(`SELECT name,age FROM person where age > ?`, age)
    if nil != err {
        panic(err)
    }
    defer rows.Close()
    for rows.Next() {
        var name string
        var age int
        err := rows.Scan(&name, &age)
        if nil != err {
            panic(err)
        }
        fmt.Println(name, age)
    }
}

这应该是最简单的使用场景了。本文也会按照以上代码,逐句展开。

import _ "somedriver"是在干什么

先来看一下golang官方文档的说法:

To import a package solely for its side-effects (initialization), use the blank identifier as explicit package name:

import _ "lib/math"

也就是说,import _ "somedriver"仅仅是想调用somedriver包的init方法。那么我们可以一起来看看go-sql-driver/mysqlinit方法。它非常简单:

func init() {
    sql.Register("mysql", &MySQLDriver{})
}

只有1行,确实非常的简单。调用sql的Register方法注册了一个名为mysql的数据库驱动,而驱动本身就是&MySQLDriver{}

那我们再看看sql包中的Register方法:

// Register makes a database driver available by the provided name.
// If Register is called twice with the same name or if driver is nil,
// it panics.
func Register(name string, driver driver.Driver) {
    driversMu.Lock()
    defer driversMu.Unlock()
    if driver == nil {
        panic("sql: Register driver is nil")
    }
    if _, dup := drivers[name]; dup {
        panic("sql: Register called twice for driver " + name)
    }
    drivers[name] = driver
}

Register的第二个参数接收一个driver.Driver的interface,因此go-sql-driver/mysql包中的&MySQLDriver必须实现driver.Driver规定的一系列方法(当然它肯定实现了)。

Register函数如果发现名为name的driver已经注册了,就会触发panic,否则就进行注册。注册其实很简单,drivers[name] = driver

drivers是一个map

drivers   = make(map[string]driver.Driver)

所以简单来说,import _ "somedriver"其实就是调用sql.Register注册一个实现了driver.Driver接口的实例。

驱动给sql包提供了最基本的支持,sql包最终与数据库打交道的操作都是通过driver完成的。其实不应该说sql包,而应该说是DB实例。

在上面程序main函数的一开始,执行sql.Open拿到了一个DB实例,那么什么是DB实例,sql.Open又干了什么?

sql.Open是在干什么

看一下官方文档的介绍:

func Open(driverName, dataSourceName string) (*DB, error)
//Open opens a database specified by its database driver name and a driver-specific data source name, usually consisting of at least a database name and connection information.

//Most users will open a database via a driver-specific connection helper function that returns a *DB. No database drivers are included in the Go standard library. See https://golang.org/s/sqldrivers for a list of third-party drivers.

//Open may just validate its arguments without creating a connection to the database. To verify that the data source name is valid, call Ping.

//The returned DB is safe for concurrent use by multiple goroutines and maintains its own pool of idle connections. Thus, the Open function should be called just once. It is rarely necessary to close a DB.

简单来说,Open返回一个DB实例,DB实例引用了由driverName指定的数据库驱动程序。DB本身维护了数据库连接池,是线程安全的。

func Open(driverName, dataSourceName string) (*DB, error) {
    driversMu.RLock()
    driveri, ok := drivers[driverName]
    driversMu.RUnlock()
    if !ok {
        return nil, fmt.Errorf("sql: unknown driver %q (forgotten import?)", driverName)
    }
    db := &DB{
        driver:   driveri,
        dsn:      dataSourceName,
        openerCh: make(chan struct{}, connectionRequestQueueSize),
        lastPut:  make(map[*driverConn]string),
    }
    go db.connectionOpener()
    return db, nil
}

Open方法:

  • 根据driverName拿到对应的driver
  • 根据driver和dataSourceName生成一个DB实例
  • 另起一个goroutine来执行某种任务A

如果对goroutine比较敏感的同学可能会猜到go db.connectionOpener()是在干嘛。在go中大多数情况下新开一个goroutine都是在:

  • 监听某个channel
  • 往某个channel发消息

根据上面的代码不难猜测,connectionOpeneropennerCh有关。看名字也很容易看出,connectionOpener翻译过来就是连接创建者,负责创建连接。看看代码吧:

// Runs in a separate goroutine, opens new connections when requested.
func (db *DB) connectionOpener() {
    for range db.openerCh {
        db.openNewConnection()
    }
}

每当从openerCh取到一条消息,connectionOpener就创建一个连接。

如何创建连接其实很简单,就是调用Driver提供的Open方法,具体先暂时不展开了。(不展开的这个决定,和golang的sql包是很吻合的,因为sql包对Open一个连接的处理,仅仅是定义了一个接口,让驱动去实现。也就是说,在逻辑上这里需要Open一个新连接,具体怎么做我不管,Driver你提供Open接口,返回给我我要的就行。)

整个DB可以画一张图来理解。


211460-b738074548aa0ba8.png

当然DB实例还有很多其它细节,但是对于sql.Open方法来说,以上就够了。总结一下,sql.Open会根据driverName和dataSourceName生成一个DB实例,并且另起一个goroutine来负责新建连接(监听openerCh的“新建连接请求”)。

在这里可以看出,执行sql.Open,仅仅返回了DB实例,但无法得知是否真的和数据库成功连接。按照文档的说法,如果要确认是否和数据库真的连接上了,需要执行Ping方法:

Open may just validate its arguments without creating a connection to the database. To verify that the data source name is valid, call Ping.

成功拿到DB对象之后,我们就可以操作数据库了。

下一篇,我们的主题将是连接池的维护what's behind the db.Query command

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,335评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,895评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,766评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,918评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,042评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,169评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,219评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,976评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,393评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,711评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,876评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,562评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,193评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,903评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,699评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,764评论 2 351

推荐阅读更多精彩内容