golang mysql driver_Golang 的 mysql 驱动的解析 (连接过程)

bdf8a6c40c8907bd7060fc8e0ab0a8d0.png

前言:go 中 import 用于导入包

进入正题之前先介绍一个小知识点:

比如:

import "fmt"

在代码中就可以使用 fmt 包中的方法,如:

fmt.Println("hello world")

1、使用点操作引入包时,可以省略包前缀:

import . "fmt"

注意上面 fmt 前多了 . 字符。代码中使用时:

Println("hello world")

前缀 fmt 就不需要了。

2、别名操作可以给包起个小名儿。如:

import f "fmt"

f.Println("hello world")

3、_操作

由于 go 在引入包时调用包的 init 方法。所以使用_操作,主要是为了使用包的 init 函数,一般用在数据库方面的包中:

import (

"database/sql"

_ "github.com/ziutek/mymysql/godrv"

)

这个可以避免 go 编译时提示引入了包但未使用。

MySQL 驱动的工作原理

SQL 驱动接口

Golang 的官方标准库提供了 database/sql,SQL 包提供了保证 SQL 或类 SQL 数据库的泛用接口。使用 SQL 包时必须注入(至少)一个数据库驱动。

Goals of the SQL and sql/driver packages:

Provide a generic database API for a variety of SQL or SQL-like

databases. There currently exist Go libraries for SQLite, MySQL,

and Postgres, but all with a very different feel, and often

a non-Go-like feel.

format,png

sql/driver 的目的是提供一个通用的数据库抽象接口,现在已经存在 SQLite,MySQL 等,但是他们的设计风格完全不同。

考虑到通用 SQL 的可移植的情况,而特定的 SQL 特性,和扩展应该被应用程序单独实现。

使用 sql/dirver 包,用户不需要考虑并发和线程安全的问题,通过 *sql.DB 可以在不同的 goroutines 之间共享,而不需要额外的同步。

MySQL 驱动

go-sql-dirver/mysql 是一个 MySQL 数据库的驱动, databases/sql 中定义的泛化接口的实现。

它需要自行安装:

go get -u github.com/go-sql-driver/mysql

import "database/sql"

import _ "github.com/go-sql-driver/mysql"

db, err := sql.Open("mysql", "user:password@/dbname")

导入 go-sql-dirver/mysql 包,这里用到前言中的最后一种导入包的情况,目的是运行 MySQL 包中 init 函数,把 MySQL 驱动注册到 database/sql 中,注册的名称是“MySQL”,对象是一个实现了 Open 方法的结构体 MysqlSQLDirver。

// in mysql packagefunc init() {

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

}

// in sql packagefunc 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

}

注册函数用到了 Sync 包中的同步工具。

同步的用途有两个,一个是避免多个线程在同一时刻操作同一个数据块,另一个是协调多个线程,以避免它们在同一时刻执行同一个代码块

在 Go 语言中,可供我们选择的同步工具并不少。其中,最重要且最常用的同步工具当属互斥量(mutual exclusion,简称 mutex。sync 包中的 Mutex 就是与其对应的类型,该类型的值可以被称为互斥量或者互斥锁。

每当线程或者 goroutine 进去临界区,抢占资源的时候就要进行 lock 操作,当离开临界区,unlock 操作。解锁最好用 defer 关键字。防止程序崩溃无法解锁的情况导致后续 goroutine 的阻塞。

// Open new Connection.// See https://github.com/go-sql-driver/mysql#dsn-data-source-name for how// the DSN string is formatedfunc (d MySQLDriver) Open(dsn string) (driver.Conn, error) {

var err error

// New mysqlConnmc := &mysqlConn{

maxAllowedPacket: maxPacketSize,

maxWriteSize: maxPacketSize - 1,

}

mc.cfg, err = ParseDSN(dsn)

if err != nil {

return nil, err

}

mc.parseTime = mc.cfg.ParseTime

mc.strict = mc.cfg.Strict

// Connect to Serverif dial, ok := dials[mc.cfg.Net]; ok {

mc.netConn, err = dial(mc.cfg.Addr)

} else {

nd := net.Dialer{Timeout: mc.cfg.Timeout}

mc.netConn, err = nd.Dial(mc.cfg.Net, mc.cfg.Addr)

}

if err != nil {

return nil, err

}

// Enable TCP Keepalives on TCP connectionsif tc, ok := mc.netConn.(*net.TCPConn); ok {

if err := tc.SetKeepAlive(true); err != nil {

// Don't send COM_QUIT before handshake.mc.netConn.Close()

mc.netConn = nil

return nil, err

}

}

mc.buf = newBuffer(mc.netConn)

// Set I/O timeoutsmc.buf.timeout = mc.cfg.ReadTimeout

mc.writeTimeout = mc.cfg.WriteTimeout

// Reading Handshake Initialization Packetcipher, err := mc.readInitPacket()

if err != nil {

mc.cleanup()

return nil, err

}

// Send Client Authentication Packetif err = mc.writeAuthPacket(cipher); err != nil {

mc.cleanup()

return nil, err

}

// Handle response to auth packet, switch methods if possibleif err = handleAuthResult(mc, cipher); err != nil {

// Authentication failed and MySQL has already closed the connection// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).// Do not send COM_QUIT, just cleanup and return the error.mc.cleanup()

return nil, err

}

if mc.cfg.MaxAllowedPacket > 0 {

mc.maxAllowedPacket = mc.cfg.MaxAllowedPacket

} else {

// Get max allowed packet sizemaxap, err := mc.getSystemVar("max_allowed_packet")

if err != nil {

mc.Close()

return nil, err

}

mc.maxAllowedPacket = stringToInt(maxap) - 1

}

if mc.maxAllowedPacket < maxPacketSize {

mc.maxWriteSize = mc.maxAllowedPacket

}

// Handle DSN Paramserr = mc.handleParams()

if err != nil {

mc.Close()

return nil, err

}

return mc, nil

}

golang 在进行数据库操作,一般来说我们使用 Open 函数创建一个数据库(操作)句柄: func Open(driverName, dataSourceName string) (DB, error) 我们知道,返回的 DB 是一个具有零到多个底层连接的连接池,可以安全的被多个 go 程使用,并且会维护自身的闲置连接。所以一般来说 open 函数我们只调用一次,几乎很少需要关闭 DB。 连接池可以使得连接更好的控制,于是简单配置甚至是不做什么配置就可以用了,事实好像也是如此,连上数据库就都 ok 了。但是!你很可能会遇到下面这样的错误: [mysql] 2017/01/15 12:23:43 packets.go:124: write tcp 127.0.0.1:45697->127.0.0.1:3306:write: broken pipe 其原因很有可能就是你的连接池没有做好相关配置,或者配置了却设置的值不对,那么超时的原因大概有以下几点: 1.最大连接数大于数据库服务器端配置的最大连接数,多余的连接在被使用到的时候出现连接超时 2.网络抖动:每次连接的时候提交或维护大量数据,此时网络不稳定导致连接超时 3.占用的资源未被释放,我们一般只 open 一次,获取一个*DB,但是在使用 stmt,rows 或者是 tx 时候用完没有关闭,耗尽资源也有可能导致连接超时。 对于出现的 3 中问题,你可以试试下面的解决方案: 1.对于最大连接数的问题,调用函数 Db.SetMaxOpenConns()设置值(小于数据库配置的最大连接) 2.对于维护大量数据,可以尝试采用事务操作,若失败,回滚。然后重试。 3.使用 defer 关键字,在启用操作的时候直接在下一行加上 defer *.close()函数 return 的时候会执行相关的关闭函数。 如果你尝试了以上的解决方案,还是发现偶尔会出现连接超时的错误的话,很可能是因为数据库本身对连接有一个超时时间的设置,如果超时时间到了数据库会单方面断掉连接,此时再用连接池内的连接进行访问就会出错, 连接池中的每个连接都维护了一个创建时间,取连接的时候会自动检查的,你可以试试调用 db 的 SetConnMaxLifetime()方法来给 db 设置一个超时时间,时间小于数据库的超时时间即可。 以上仅是个人的一下看法,若有错误之处,欢迎批评指出。如果你尝试了以上的解决方案,还是发现偶尔会出现连接超时的错误的话,很可能是因为数据库本身对连接有一个超时时间的设置,如果超时时间到了数据库会单方面断掉连接,此时再用连接池内的连接进行访问就会出错,

自己看了一下 databas/sql 和 go-sql-driver 的源码,出现这个错误之后官方提供的方法是会进行两次的重连操作,如果两次重连都没有成功,就创建一个新的 SQL 链接,所以出现这个错误日志没什么影响,当然最好的做法是设置一下链接的空闲超时时间,设置的比 MySQL 服务器超时时间短就可以了,设置了这个之后就不会报这个错误了。当然不设也可以,设置了的话程序处理会更优雅一点!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值