在上一篇文章中主要整理了Golang连接mysql以及一些基本的操作,并进行了大概介绍,这篇文章对增删查改进行详细的整理

读取数据

在上一篇文章中整理查询数据的时候,使用了Query的方法查询,其实database/sql还提供了QueryRow方法查询数据,就像之前说的database/sql连接创建都是惰性的,所以当我们通过Query查询数据的时候主要分为三个步骤:

  1. 从连接池中请求一个连接
  2. 执行查询的sql语句
  3. 将数据库连接的所属权传递给Result结果集

Query返回的结果集是sql.Rows类型。它有一个Next方法,可以迭代数据库的游标,进而获取每一行的数据,使用方法如下:

//执行查询操作
rows,err := Db.Query("SELECT email FROM user_info WHERE user_id>=5")
if err != nil{
fmt.Println("select db failed,err:",err)
return
}
// 这里获取的rows是从数据库查的满足user_id>=5的所有行的email信息,rows.Next(),用于循环获取所有
for rows.Next(){
var s string
err = rows.Scan(&s)
if err != nil{
fmt.Println(err)
return
}
fmt.Println(s)
}
rows.Close()

其实当我们通过for循环迭代数据库的时候,当迭代到最后一样数据的时候,会出发一个io.EOF的信号,引发一个错误,同时go会自动调用rows.Close方法释放连接,然后返回false,此时循环将会结束退出。

通常你会正常迭代完数据然后退出循环。可是如果并没有正常的循环而因其他错误导致退出了循环。此时rows.Next处理结果集的过程并没有完成,归属于rows的连接不会被释放回到连接池。因此十分有必要正确的处理rows.Close事件。如果没有关闭rows连接,将导致大量的连接并且不会被其他函数重用,就像溢出了一样。最终将导致数据库无法使用。

所以为了避免这种情况的发生,最好的办法就是显示的调用rows.Close方法,确保连接释放,又或者使用defer指令在函数退出的时候释放连接,即使连接已经释放了,rows.Close仍然可以调用多次,是无害的。

rows.Next循环迭代的时候,因为触发了io.EOF而退出循环。为了检查是否是迭代正常退出还是异常退出,需要检查rows.Err。例如上面的代码应该改成:

//Query执行查询操作
rows,err := Db.Query("SELECT email FROM user_info WHERE user_id>=5")
if err != nil{
fmt.Println("select db failed,err:",err)
return
}
// 这里获取的rows是从数据库查的满足user_id>=5的所有行的email信息,rows.Next(),用于循环获取所有
for rows.Next(){
var s string
err = rows.Scan(&s)
if err != nil{
fmt.Println(err)
return
}
fmt.Println(s)
}
rows.Close()
if err = rows.Err();err != nil{
fmt.Println(err)
return
}

读取单条数据

Query方法是读取多行结果集,实际开发中,很多查询只需要单条记录,不需要再通过Next迭代。golang提供了QueryRow方法用于查询单条记录的结果集。

QueryRow方法的使用很简单,它要么返回sql.Row类型,要么返回一个error,如果是发送了错误,则会延迟到Scan调用结束后返回,如果没有错误,则Scan正常执行。只有当查询的结果为空的时候,会触发一个sql.ErrNoRows错误。你可以选择先检查错误再调用Scan方法,或者先调用Scan再检查错误。

在之前的代码中我们都用到了Scan方法,下面说说关于这个方法

结果集方法Scan可以把数据库取出的字段值赋值给指定的数据结构。它的参数是一个空接口的切片,这就意味着可以传入任何值。通常把需要赋值的目标变量的指针当成参数传入,它能将数据库取出的值赋值到指针值对象上。
代码例子如:

// 查询数据
var username string
var email string
rows := Db.QueryRow("SELECT username,email FROM user_info WHERE user_id=6")
err = rows.Scan(&username,&email)
if err != nil{
fmt.Println("scan err:",err)
return
}
fmt.Println(username,email)

Scan还会帮我们自动推断除数据字段匹配目标变量。比如有个数据库字段的类型是VARCHAR,而他的值是一个数字串,例如"1"。如果我们定义目标变量是string,则scan赋值后目标变量是数字string。如果声明的目标变量是一个数字类型,那么scan会自动调用strconv.ParseInt()或者strconv.ParseInt()方法将字段转换成和声明的目标变量一致的类型。当然如果有些字段无法转换成功,则会返回错误。因此在调用scan后都需要检查错误。

空值处理

数据库有一个特殊的类型,NULL空值。可是NULL不能通过scan直接跟普遍变量赋值,甚至也不能将null赋值给nil。对于null必须指定特殊的类型,这些类型定义在database/sql库中。例如sql.NullFloat64,sql.NullString,sql.NullBool,sql.NullInt64。如果在标准库中找不到匹配的类型,可以尝试在驱动中寻找。下面是一个简单的例子:

下面代码,数据库中create_time为Null这个时候,如果直接这样查询,会提示错误:

// 查询数据
var username string
var email string
var createTime string
rows := Db.QueryRow("SELECT username,email,create_time FROM user_info WHERE user_id=6")
err = rows.Scan(&username,&email,&createTime)
if err != nil{
fmt.Println("scan err:",err)
return
}
fmt.Println(username,email,createTime)

错误内容如下:

scan err: sql: Scan error on column index 2: unsupported Scan, storing driver.Value type <nil> into type *string

所以需要将代码更改为:

// 查询数据
var username string
var email string
var createTime sql.NullString
rows := Db.QueryRow("SELECT username,email,create_time FROM user_info WHERE user_id=6")
err = rows.Scan(&username,&email,&createTime)
if err != nil{
fmt.Println("scan err:",err)
return
}
fmt.Println(username,email,createTime)

执行结果为:

user01 8989@qq.com { false}

我将数据库中添加了一列,是int类型,同样的默认值是Null,代码为:

// 查询数据
var username string
var email string
var createTime string
var score int
rows := Db.QueryRow("SELECT username,email,create_time,socre FROM user_info WHERE user_id=6")
rows.Scan(&username,&email,&createTime,&score)
fmt.Println(username,email,createTime,score)

其实但我们忽略错误直接输出的时候,也可以输出,当然Null的字段都被转换为了零值
而当我们按照上面的方式处理后,代码为:

// 查询数据
var username string
var email string
var createTime sql.NullString
var score sql.NullInt64
rows := Db.QueryRow("SELECT username,email,create_time,socre FROM user_info WHERE user_id=6")
err = rows.Scan(&username,&email,&createTime,&score)
if err != nil{
fmt.Println("scan fail,err:",err)
return
}
fmt.Println(username,email,createTime,score)

输出的结果为:

user01 8989@qq.com { false} {0 false}

对Null的操作,一般还是需要验证的,代码如下:

// 查询数据
var score sql.NullInt64
rows := Db.QueryRow("SELECT socre FROM user_info WHERE user_id=6")
err = rows.Scan(&score)
if err != nil{
fmt.Println("scan fail,err:",err)
return
}
if score.Valid{
fmt.Println("res:",score.Int64)
}else{
fmt.Println("err",score.Int64)
}

这里我已经在数据库给字段添加内容了,所以这里默认输出10,但是当还是Null的时候输出的则是零值
但是有时候我们如果不关心是不是Null的时候,只是想把它当做空字符串处理就行,我们也可以使用[]byte,代码如下:

// 查询数据
var score []byte
var modifyTime []byte
rows := Db.QueryRow("SELECT modify_time,socre FROM user_info WHERE user_id=6")
err = rows.Scan(&modifyTime,&score)
if err != nil{
fmt.Println("scan fail,err:",err)
return
}
fmt.Println(string(modifyTime),string(score))

这样处理后,如果有值则可以获取值,如果没有则获取的为空字符串

自动匹配字段

上面查询的例子中,我们都自己定义了变量,同时查询的时候也写明了字段,如果不指名字段,或者字段的顺序和查询的不一样,都有可能出错。因此如果能够自动匹配查询的字段值,将会十分节省代码,同时也易于维护。
go提供了Columns方法用获取字段名,与大多数函数一样,读取失败将会返回一个err,因此需要检查错误。
代码例子如下:

// 查询数据

rows,err:= Db.Query("SELECT * FROM user_info WHERE user_id>6")
if err != nil{
fmt.Println("select fail,err:",err)
return
}
cols,err := rows.Columns()
if err != nil{
fmt.Println("get columns fail,err:",err)
return
}
fmt.Println(cols)
vals := make([][]byte, len(cols))
scans := make([]interface{},len(cols)) for i := range vals{
scans[i] = &vals[i]
}
fmt.Println(scans)
var results []map[string]string for rows.Next(){
err = rows.Scan(scans...)
if err != nil{
fmt.Println("scan fail,err:",err)
return
}
row := make(map[string]string)
for k,v:=range vals{
key := cols[k]
row[key] =string(v)
}
results = append(results,row)
} for k,v:=range results{
fmt.Println(k,v)
}

因为查询的时候是语句是:
SELECT * FROM user_info WHERE user_id>6
这样就会获取每行数据的所有的字段
使用rows.Columns()获取字段名,是一个string的数组
然后创建一个切片vals,用来存放所取出来的数据结果,类似是byte的切片。接下来还需要定义一个切片,这个切片用来scan,将数据库的值复制到给它
vals则得到了scan复制给他的值,因为是byte的切片,因此在循环一次,将其转换成string即可。
转换后的row即我们取出的数据行值,最后组装到result切片中。

上面代码的执行结果为:

[user_id username sex email create_time modify_time socre]
[0xc4200c6000 0xc4200c6018 0xc4200c6030 0xc4200c6048 0xc4200c6060 0xc4200c6078 0xc4200c6090]
0 map[user_id:7 username:user01 sex:男 email:23333222@qq.com create_time:2018-03-05 14:10:08 modify_time: socre:]
1 map[username:user11 sex:男 email:1231313@qq.com create_time:2018-03-05 14:10:11 modify_time: socre: user_id:8]
2 map[sex:男 email:65656@qq.com create_time:2018-03-05 14:10:15 modify_time: socre: user_id:9 username:user12]

通过上面例子的整理以及上面文章的整理,我们基本可以知道:
Exec的时候通常用于执行插入和更新操作
Query以及QueryRow通常用于执行查询操作

Exec执行完毕之后,连接会立即释放回到连接池中,因此不需要像query那样再手动调用row的close方法。

Go基础之--操作Mysql(二)的更多相关文章

  1. Go基础之--操作Mysql(一)

    关于标准库database/sql database/sql是golang的标准库之一,它提供了一系列接口方法,用于访问关系数据库.它并不会提供数据库特有的方法,那些特有的方法交给数据库驱动去实现. ...

  2. Go语言基础之操作MySQL

    Go语言操作MySQL MySQL是常用的关系型数据库,本文介绍了Go语言如何操作MySQL数据库. Go操作MySQL 连接 Go语言中的database/sql包提供了保证SQL或类SQL数据库的 ...

  3. Go基础之--操作Mysql(三)

    事务是数据库的一个非常重要的特性,尤其对于银行,支付系统,等等.database/sql提供了事务处理的功能.通过Tx对象实现.db.Begin会创建tx对象,后者的Exec和Query执行事务的数据 ...

  4. python操作mysql二

    游标 游标是一种能从包括多条数据记录的结果集中每次提取一条记录的机制,游标充当指针的作用,尽管游标能遍历结果中的所有行,但它一次只指向一行,游标的作用就是用于对查询数据库所返回的记录进行遍历,以便进行 ...

  5. MySQL之命令行简单操作MySQL(二)

    一:命令行连接数据库 打开终端,运行命令mysql -uroot -p (p后面加密码,可以直接加,也可以回车在下一行输入,为了不暴露密码,回车在下行输入 退出:exit或quit 查看版本信息: s ...

  6. webpack4基础入门操作(二)(讲解下webpack的配置内容)

    前序:我之所以开始写这个系列,是因为我最近出去看了下外面的情况,发现技术更新的脚步太快了.我的技术栈已经完全落伍了. 所以准备今年学习写新的东西,而React.webPack4就是我的第一步.前面我看 ...

  7. python 操作 mysql基础补充

    前言 本篇的主要内容为整理mysql的基础内容,分享的同时方便日后查阅,同时结合python的学习整理python操作mysql的方法以及python的ORM. 一.数据库初探 在开始mysql之前先 ...

  8. Mysql的二进制安装和基础入门操作

    前言:Mysql数据库,知识非常的多,要想学精学通这块知识,估计也要花费和学linux一样的精力和时间.小编也是只会些毛皮,给大家分享一下~ 一.MySQL安装 (1)安装方式: 1 .程序包yum安 ...

  9. Mysql数据库的二进制安装和基础入门操作

    前言:Mysql数据库,知识非常的多,要想学精学通这块知识,估计也要花费和学linux一样的精力和时间.小编也是只会些毛皮,给大家分享一下~ 一.MySQL安装 (1)安装方式: 1 .程序包yum安 ...

随机推荐

  1. 安卓studio导入jra包和so包,百度地图so包加载

    导入so包 这个我只接受测试可用的一种方法 第一步:把so包放在libs目录下,可以是文件夹也可以是单独的一个个so文件 然后在src同级的目录下找到build.gradle文件下如下信息 sourc ...

  2. 【转】GAMITBLOBK中固定解、浮点解、约束解、松弛解等解类型解释

    在GAMIT/GLOBK的使用过程中,经常会碰到固定解.浮点解.约束解.松弛解及其相关组合解(如约束固定解)等词汇,对于初学者,一时难以弄明白其中的含义,一般只有按部就班按照教程中,怎么说就怎么弄,不 ...

  3. IT 人士如何避免中年危机?

    今天咱们不谈技术,来聊点别的. 这也可能是比学习具体技术更重要的话题 - 投资. 我把投资分成两类: 投资股票.期货.现货.黄金这类常见投资品种. 投资自己.比如看书.学习.参加培训.当然<每天 ...

  4. python脚本获取主机Mac地址

    #!/usr/bin/python import re import subprocess ARP = "arp" IP = "192.168.128.27" ...

  5. 查看Eclipse运行工程时使用的Command Line

    一.查看使用的Command Line 1.Window -> Show View -> Other... -> Debug 2.运行工程,然后在Debug视窗中找到运行的工程的主线 ...

  6. C#基础(三)--运算符及条件控制语句

    上一章我们了解了C#的一些数据基本类型.如何定义使用变量及类型之间的相互转换.可能大家会发现程序只能按照我们的要求从上向下这样顺序一条一条的执行. 那么程序能按照我们的要求,当某个条件成立的时候才执行 ...

  7. 23_迭代器、模拟For循环

    一.可迭代对象 和 迭代器 1.可迭代对象和迭代器 可迭代对象:可以直接作用于for循环的对象统称为可迭代对象,Iterable. 迭代器:可以被next()函数调用并不断返回下一个值的对象称为迭代器 ...

  8. iOS UITableView左滑操作功能的实现(iOS8-11)

    WeTest 导读 本文主要是介绍下iOS 11系统及iOS 11之前的系统在实现左滑操作功能上的区别,及如何自定义左滑的标题颜色.字体大小. 一.左滑操作功能实现 1.如果左滑的时候只有一个操作按钮 ...

  9. Uva 11077 Find the Permutations [置换群 DP]

    题意: 给定$n$和$k$,问有多少排列交换$k$次能变成升序 $n \le 21$ $uva$貌似挂掉了$vjudge$上一直排队 从某个排列到$1,2,...,n$和从$1,2,...,n$到某个 ...

  10. SPSS 批量添加标签