关于 Go 的标准库 database/sql 和 sqlx

database/sql 是 Go 操作数据库的标准库之一,它提供了一系列接口方法,用于访问数据库(mysql,sqllite,oralce,postgresql),它并不会提供数据库特有的方法,那些特有的方法交给数据库驱动去实现

而通常在工作中,我们更多的是用  https://github.com/jmoiron/sqlx 包来操作数据库,sqlx 是基于标准库 sql 的扩展,并且我们可以通过 sqlx 操作各种类型的数据,如将查询的数据转为结构体等

github 地址:

  • https://github.com/go-sql-driver/mysql
  • https://github.com/jmoiron/sqlx

安装:

go get "github.com/go-sql-driver/mysql"
go get "github.com/jmoiron/sqlx"

sqlx 库提供了一些类型,掌握这些类型的用法非常的重要

1)DB(数据库对象)

sql.DB 类型代表了数据库,其它语言操作数据库的时候,需要创建一个连接,对于 Go 而言则是需要创建一个数据库类型,它不是数据库连接,Go 中的连接来自内部实现的连接池,连接的建立是惰性的,连接将会在操作的时候,由连接池创建并维护

使用 sql.Open 函数创建数据库类型,第一个是数据库驱动名,第二个是连接信息的字符串

var Db *sqlx.DB
db, err := sqlx.Open("mysql","username:password@tcp(ip:port)/database?charset=utf8")
Db = db

2)Results 和 Result(结果集)

新增、更新、删除;和查询所用的方法不一样,所有返回的类型也不同

  • Result 是 新增、更新、删除时返回的结果集
  • Results 是查询数据库时的结果集,sql.Rows 类型表示查询返回多行数据的结果集,sql.Row 则表示单行查询的结果集

3)Statements(语句)

sql.Stmt 类型表示 sql 语句,例如 DDL,DML 等类似的 sql 语句,可以当成 prepare 语句构造查询,也可以直接使用 sql.DB 的函数对其操作

实践部分(数据库CURD)

数据库建表

以下所有 demo 都以下表结构作为基础

CREATE TABLE `userinfo` (
`uid` INT(10) NOT NULL AUTO_INCREMENT,
`create_time` datetime DEFAULT NULL,
`username` VARCHAR(64) DEFAULT NULL,
`password` VARCHAR(32) DEFAULT NULL,
`department` VARCHAR(64) DEFAULT NULL,
`email` varchar(64) DEFAULT NULL,
PRIMARY KEY (`uid`)
)ENGINE=InnoDB DEFAULT CHARSET=utf8;

Exec() 方法使用(新增、修改、删除)

func (db *DB) Exec(query string, args ...interface{}) (Result, error)

Exec 和 MustExec 从连接池中获取一个连接然后指向对应的 query 操作,对于不支持 ad-hoc query execution 的驱动,在操作执行的背后会创建一个 prepared statement,在结果返回前,这个 connection 会返回到连接池中

需要注意的是,不同的数据库,使用的占位符不同,mysql 采用 ? 作为占位符

  • Mysql 使用 ?
  • PostgreSQL 使用 1,1,2 等等
  • SQLLite 使用 ? 或 $1
  • Oracle 使用 :name        (注意有冒号)

demo:定义了 4 个函数,分别是 连接数据库,插入数据,更新数据,删除数据

关于 下面数据库操作的几个小知识点

  1. 插入数据后可以通过 LastInsertId() 方法获取插入数据的主键 id
  2. 通过 RowsAffected 可以获取受影响的行数
  3. 通过 Exec() 方法插入数据,返回的结果是 sql.Result 类型
package main

import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
) var (
userName string = "chenkai"
password string = "chenkai"
ipAddrees string = "192.168.0.115"
port int = 3306
dbName string = "test"
charset string = "utf8"
) func connectMysql() (*sqlx.DB) {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset)
Db, err := sqlx.Open("mysql", dsn)
if err != nil {
fmt.Printf("mysql connect failed, detail is [%v]", err.Error())
}
return Db
} func addRecord(Db *sqlx.DB) {
for i:=0; i<2; i++ {
result, err := Db.Exec("insert into userinfo values(?,?,?,?,?,?)",0, "2019-07-06 11:45:20", "johny", "123456", "技术部", "123456@163.com")
if err != nil {
fmt.Printf("data insert faied, error:[%v]", err.Error())
return
}
id, _ := result.LastInsertId()
fmt.Printf("insert success, last id:[%d]\n", id)
}
} func updateRecord(Db *sqlx.DB){
//更新uid=1的username
result, err := Db.Exec("update userinfo set username = 'anson' where uid = 1")
if err != nil {
fmt.Printf("update faied, error:[%v]", err.Error())
return
}
num, _ := result.RowsAffected()
fmt.Printf("update success, affected rows:[%d]\n", num)
} func deleteRecord(Db *sqlx.DB){
//删除uid=2的数据
result, err := Db.Exec("delete from userinfo where uid = 2")
if err != nil {
fmt.Printf("delete faied, error:[%v]", err.Error())
return
}
num, _ := result.RowsAffected()
fmt.Printf("delete success, affected rows:[%d]\n", num)
} func main() {
var Db *sqlx.DB = connectMysql()
defer Db.Close() addRecord(Db)
updateRecord(Db)
deleteRecord(Db)
} 运行结果:
API server listening at: 127.0.0.1:59899
insert success, last id:[1]
insert success, last id:[2]
update success, affected rows:[1]
delete success, affected rows:[1]

  

Query() 方法使用(查询单个字段数据)

func (db *DB) Query(query string, args ...interface{}) (*Rows, error)

Query() 方法返回的是一个 sql.Rows 类型的结果集

也可以用来查询多个字段的数据,不过需要定义多个字段的变量进行接收

迭代后者的 Next() 方法,然后使用 Scan() 方法给对应类型变量赋值,以便取出结果,最后再把结果集关闭(释放连接)

package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
) var (
userName string = "chenkai"
password string = "chenkai"
ipAddrees string = "192.168.0.115"
port int = 3306
dbName string = "test"
charset string = "utf8"
) func connectMysql() (*sqlx.DB) {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset)
Db, err := sqlx.Open("mysql", dsn)
if err != nil {
fmt.Printf("mysql connect failed, detail is [%v]", err.Error())
}
return Db
} func queryData(Db *sqlx.DB) {
rows, err := Db.Query("select * from userinfo")
if err != nil {
fmt.Printf("query faied, error:[%v]", err.Error())
return
}
for rows.Next() {
//定义变量接收查询数据
var uid int
var create_time, username, password, department, email string err := rows.Scan(&uid, &create_time, &username, &password, &department, &email)
if err != nil {
fmt.Println("get data failed, error:[%v]", err.Error())
}
fmt.Println(uid, create_time, username, password, department, email)
} //关闭结果集(释放连接)
rows.Close()
} func main() {
var Db *sqlx.DB = connectMysql()
defer Db.Close() queryData(Db)
} 运行结果:
1 2019-07-06 11:45:20 anson 123456 技术部 123456@163.com
3 2019-07-06 11:45:20 johny 123456 技术部 123456@163.com
4 2019-07-06 11:45:20 johny 123456 技术部 123456@163.com

Get() 方法使用

func (db *DB) Get(dest interface{}, query string, args ...interface{}) error

是将查询到的一条记录,保存到结构体

结构体的字段名首字母必须大写,不然无法寻址

package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
) var (
userName string = "chenkai"
password string = "chenkai"
ipAddrees string = "192.168.0.115"
port int = 3306
dbName string = "test"
charset string = "utf8"
) func connectMysql() (*sqlx.DB) {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset)
Db, err := sqlx.Open("mysql", dsn)
if err != nil {
fmt.Printf("mysql connect failed, detail is [%v]", err.Error())
}
return Db
} func getData(Db *sqlx.DB) {
type userInfo struct {
Uid int `db:"uid"`
UserName string `db:"username"`
CreateTime string `db:"create_time"`
Password string `db:"password"`
Department string `db:"department"`
Email string `db:"email"`
} //初始化定义结构体,用来存放查询数据
var userData *userInfo = new(userInfo)
err := Db.Get(userData,"select *from userinfo where uid = 1")
if err != nil {
fmt.Printf("query faied, error:[%v]", err.Error())
return
} //打印结构体内容
fmt.Println(userData.Uid, userData.CreateTime, userData.UserName,
userData.Password, userData.Department, userData.Email)
} func main() {
var Db *sqlx.DB = connectMysql()
defer Db.Close() getData(Db)
} 运行结果:
1 2019-07-06 11:45:20 anson 123456 技术部 123456@163.com

Select() 方法使用

func (db *DB) Select(dest interface{}, query string, args ...interface{}) error

将查询的多条记录,保存到结构体的切片中

结构体的字段名首字母必须大写,不然无法寻址

package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
) var (
userName string = "chenkai"
password string = "chenkai"
ipAddrees string = "192.168.0.115"
port int = 3306
dbName string = "test"
charset string = "utf8"
) func connectMysql() (*sqlx.DB) {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset)
Db, err := sqlx.Open("mysql", dsn)
if err != nil {
fmt.Printf("mysql connect failed, detail is [%v]", err.Error())
}
return Db
} func selectData(Db *sqlx.DB) {
type userInfo struct {
Uid int `db:"uid"`
UserName string `db:"username"`
CreateTime string `db:"create_time"`
Password string `db:"password"`
Department string `db:"department"`
Email string `db:"email"`
} //定义结构体切片,用来存放多条查询记录
var userInfoSlice []userInfo
err := Db.Select(&userInfoSlice,"select * from userinfo")
if err != nil {
fmt.Printf("query faied, error:[%v]", err.Error())
return
} //遍历结构体切片
for _, userData := range userInfoSlice {
fmt.Println(userData.Uid, userData.CreateTime, userData.UserName,
userData.Password, userData.Department, userData.Email)
} } func main() {
var Db *sqlx.DB = connectMysql()
defer Db.Close() selectData(Db)
} 运行结果:
1 2019-07-06 11:45:20 anson 123456 技术部 123456@163.com
3 2019-07-06 11:45:20 johny 123456 技术部 123456@163.com
4 2019-07-06 11:45:20 johny 123456 技术部 123456@163.com

重点内容回顾

sql.DB

  1. 当我们调用 sqlx.Open() 可以获取一个 sql.DB 类型对象,sqlx.DB 是数据库的抽象,切记它不是数据库连接,sqlx.Open() 只是验证数据库参数,并没有创建数据库连接
  2. sqlx.DB 拥有一系列与数据库交互的方法(Exec,Query,Get,Select ...),同时也管理维护着一个数据库连接池,并且对于多个 goroutine 也是安全的
  3. sqlx.DB 表示是数据库的抽象,因此有几个数据库就要创建几个 sqlx.DB 类型对象,因为它要维护一个连接池,因此不需要频繁的创建和销毁

连接池

只用 sqlx.Open() 函数创建连接池,此时只是初始化了连接池,并没有连接数据库,连接都是惰性的,只有调用 sqlx.DB 的方法时,此时才真正用到了连接,连接池才会去创建连接,连接池很重要,它直接影响着你的程序行为

连接池的工作原理也非常简单,当调用 sqlx.DB 的方法时,会首先去向连接池请求要一个数据库连接,如果连接池有空闲的连接,则返回给方法中使用,否则连接池将创建一个新的连接给到方法中使用;一旦将数据库连接给到了方法中,连接就属于方法了。方法执行完毕后,要不把连接所属权还给连接池,要不传递给下一个需要数据库连接的方法中,最后都使用完将连接释放回到连接池中

请求数据库连接的方法有几个,执行完毕处理连接的方式也不同:

  1. DB.Ping() 使用完毕后会马上把连接返回给连接池
  2. DB.Exec() 使用完毕后会马上把连接返回给连接池,但是它返回的 Result 对象还保留着连接的引用,当后面的代码需要处理结果集的时候,连接将会被重新启用
  3. DB.Query() 调用完毕后将连接传递给 sql.Rows 类型,当后者迭代完毕或者显示的调用 Close() 方法后,连接将会被释放到连接池
  4. DB.QueryRow() 调用完毕后将连接传递给 sql.Row 类型,当 Scan() 方法调用完成后,连接将会被释放到连接池
  5. DB.Begin() 调用完毕后将连接传递给 sql.Tx 类型对象,当 Commit() 或 Rollback() 方法调用后释放连接

每个连接都是惰性的,如果验证 sqlx.Open() 调用之后,sqlx.DB 类型对象可用呢?通过 DB.Ping() 方法来初始化

func (db *DB) Ping() error

demo:需要知道,当调用了 Ping() 方法后,连接池一定会初始化一个数据库连接

package main
import (
"fmt"
_ "github.com/go-sql-driver/mysql"
"github.com/jmoiron/sqlx"
) var (
userName string = "chenkai"
password string = "chenkai"
ipAddrees string = "192.168.0.115"
port int = 3306
dbName string = "test"
charset string = "utf8"
) func connectMysql() (*sqlx.DB) {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s", userName, password, ipAddrees, port, dbName, charset)
Db, err := sqlx.Open("mysql", dsn)
if err != nil {
fmt.Printf("mysql connect failed, detail is [%v]", err.Error())
}
return Db
} func ping(Db *sqlx.DB) {
err := Db.Ping()
if err != nil {
fmt.Println("ping failed")
} else {
fmt.Println("ping success")
}
} func main() {
var Db *sqlx.DB = connectMysql()
defer Db.Close() ping(Db)
} 运行结果:
ping success

连接池配置

DB.SetMaxIdleConns(n int) 设置连接池中的保持连接的最大连接数。默认也是0,表示连接池不会保持数据库连接的状态:即当连接释放回到连接池的时候,连接将会被关闭。这会导致连接再连接池中频繁的关闭和创建,我们可以设置一个合理的值。

DB.SetMaxOpenConns(n int) 设置打开数据库的最大连接数。包含正在使用的连接和连接池的连接。如果你的方法调用 需要用到一个连接,并且连接池已经没有了连接或者连接数达到了最大连接数。此时的方法调用将会被 block,直到有可用的连接才会返回。设置这个值可以避免并发太高导致连接 mysql 出现 too many connections 的错误。该函数的默认设置是0,表示无限制

DB.SetConnMaxLifetime(d time.Duration) 设置连接可以被使用的最长有效时间,如果过期,连接将被拒绝

数据库连接重试次数

sqlx 中的方法帮我们做了很多事情,我们不用考虑连接失败的情况,当调用方法进行数据库操作的时候,如果连接失败,sqlx 中的方法会帮我们处理,它会自动连接2次,这个如果查看源码中我们可以看到如下的代码:

其它的方法中也有这种处理,代码中变量maxBadConnRetries小时如果连接失败尝试的次数,默认是 2

// ExecContext executes a query without returning any rows.
// The args are for any placeholder parameters in the query.
func (db *DB) ExecContext(ctx context.Context, query string, args ...interface{}) (Result, error) {
var res Result
var err error
for i := 0; i < maxBadConnRetries; i++ {
res, err = db.exec(ctx, query, args, cachedOrNewConn)
if err != driver.ErrBadConn {
break
}
}
if err == driver.ErrBadConn {
return db.exec(ctx, query, args, alwaysNewConn)
}
return res, err
}

  

参考链接:https://www.cnblogs.com/zhaof/p/8509164.html

ending ~

Go 操作 Mysql(一)的更多相关文章

  1. ASP.NET Core 1.0 使用 Dapper 操作 MySql(包含事务)

    操作 MySql 数据库使用MySql.Data程序包(MySql 开发,其他第三方可能会有些问题). project.json 代码: { "version": "1. ...

  2. Python(九) Python 操作 MySQL 之 pysql 与 SQLAchemy

    本文针对 Python 操作 MySQL 主要使用的两种方式讲解: 原生模块 pymsql ORM框架 SQLAchemy 本章内容: pymsql 执行 sql 增\删\改\查 语句 pymsql ...

  3. EF操作MySql

    EF的CodeFrist操作MySql的提前准备: 1.安装两个包:MySql.Data和MySql.Data.Entity,在VS中程序包管理器中添加2个包.(备注需要的VS2015,并且EF6支持 ...

  4. .NET Core 使用Dapper 操作MySQL

    MySQL官方驱动:http://www.cnblogs.com/linezero/p/5806814.html .NET Core 使用Dapper 操作MySQL 数据库, .NET Core 使 ...

  5. asp.net core 1.1 升级后,操作mysql出错的解决办法。

    遇到问题 core的版本从1.0升级到1.1,操作mysql数据库,查询数据时遇到MissingMethodException问题,更新.插入操作没有问题. 如果你也遇到这个问题,请参照以下步骤进行升 ...

  6. 练习:python 操作Mysql 实现登录验证 用户权限管理

    python 操作Mysql 实现登录验证 用户权限管理

  7. Python操作MySQL

    本篇对于Python操作MySQL主要使用两种方式: 原生模块 pymsql ORM框架 SQLAchemy pymsql pymsql是Python中操作MySQL的模块,其使用方法和MySQLdb ...

  8. Python中操作mysql的pymysql模块详解

    Python中操作mysql的pymysql模块详解 前言 pymsql是Python中操作MySQL的模块,其使用方法和MySQLdb几乎相同.但目前pymysql支持python3.x而后者不支持 ...

  9. java分享第十七天-03(封装操作mysql类)

     JAVA操作mysql所需jar包:mysql-connector-java.jar代码: import java.sql.*; import java.util.ArrayList; import ...

  10. LightMysql:为方便操作MySQL而封装的Python类

    原文链接:http://www.danfengcao.info/python/2015/12/26/lightweight-python-mysql-class.html mysqldb是Python ...

随机推荐

  1. 算力和AI-ZILLIZ

    特征向量检索加速.https://www.zilliz.com/ 公司介绍: MegaWise异构众核加速数据库 MegaWise是ZILLIZ独立自主研发的新一代异构众核加速数据库系统.MegaWi ...

  2. [转][osg]探索未知种族之osg类生物【目录】

    作者:3wwang 原文链接:http://www.3wwang.cn/html/article_58.html 前序 探索未知种族之osg类生物---起源 ViewBase::frame函数中的Vi ...

  3. postgre查询表和记录数,查表字段

    select relname as TABLE_NAME, reltuples as rowCounts from pg_class where relkind = 'r' and relnamesp ...

  4. flutter 路由动画

    import 'package:flutter/material.dart'; import 'package:flutter_app/pages/FirstPage.dart'; void main ...

  5. 记一次腾讯云MySQL数据库数据回滚

    如题,因为操作人员的问题,需要对数据库数据进行回滚. 可以看到,设置了7天自动备份,且是物理冷备. 什么是物理冷备?科普一下: (1)热备:在数据库运行时,直接进行备份,对运行的数据库没有影响.(2) ...

  6. linux添加动态库路劲

    修改这个文件/etc/ld.so.conf.d,最后加上so的绝对路径即可

  7. 在 ServiceModel 客户端配置部分中,找不到引用协定“WebServiceSoap”的默认终结点元素。这可能是因为未找到应用程序的配置文件,或者是因为客户端元素找不到与此协定匹配的终结点元素(转)

    按语: 在项目中实现自动升级过程,在类库中调用webservice取升级update.xml文件,添加服务调用,但在类库中调用时就出现异常,但在简单的测试工程中没有问题.解决方法采用下面介绍的方法 在 ...

  8. [ kvm ] 学习笔记 4:KVM 高级功能详解

    1. 半虚拟化驱动 1.1 virtio 概述 KVM 是必须使用硬件虚拟化辅助技术(如 Intel VT-x .AMD-V)的 Hypervisor,在CPU 运行效率方面有硬件支持,其效率是比较高 ...

  9. prometheus数据格式

    注意区分以下两种“数据格式”: 1.自定义exporter的时候所需要遵循的给prometheus提供数据的数据格式: https://yunlzheng.gitbook.io/prometheus- ...

  10. 【Leetcode_easy】938. Range Sum of BST

    problem 938. Range Sum of BST 参考 1. Leetcode_easy_938. Range Sum of BST; 完