前言: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.
-
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 package
func init() {
sql.Register("mysql", &MySQLDriver{})
}
// in sql package
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
}
注册函数用到了 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 formated
func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
var err error
// New mysqlConn
mc := &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 Server
if 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 connections
if 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 timeouts
mc.buf.timeout = mc.cfg.ReadTimeout
mc.writeTimeout = mc.cfg.WriteTimeout
// Reading Handshake Initialization Packet
cipher, err := mc.readInitPacket()
if err != nil {
mc.cleanup()
return nil, err
}
// Send Client Authentication Packet
if err = mc.writeAuthPacket(cipher); err != nil {
mc.cleanup()
return nil, err
}
// Handle response to auth packet, switch methods if possible
if 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 size
maxap, 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 Params
err = 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 服务器超时时间短就可以了,设置了这个之后就不会报这个错误了。当然不设也可以,设置了的话程序处理会更优雅一点!
https://blog.csdn.net/pangudashu/article/details/54291558#commentBox
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于