go 数据库操作

一、mysql 操作

  • sql.DB 通过数据库驱动,提供管理底层数据库连接的打开和关闭操作.
  • sql.DB 为我们管理数据库连接池
  • 需要注意的是,sql.DB表示操作数据库的抽象访问接口,
    而非一个数据库连接对象;它可以根据driver打开关闭数据库连接,管理连接池。
    正在使用的连接被标记为繁忙,用完后回到连接池等待下次使用。所以,
    如果你没有把连接释放回连接池,会导致过多连接使系统资源耗尽。

1、导入驱动

import (
   "database/sql"
   _ "github.com/go-sql-driver/mysql"
)
  • go-sql-driver/mysql使用了匿名导入的方式(在包路径前添加 _),
    当导入了一个数据库驱动后, 此驱动会自行初始化并注册自己到Golang的database/sql上下文中

2、连接数据库

func main() {
    Dsn:= "user:password@tcp(127.0.0.1:3306)/test",
    db, err := sql.Open("mysql",Dsn)
    if err != nil {
        panic(err)
        return
    }
    defer db.Close()
}
  • 通过调用sql.Open函数返回一个sql.DB指针,获得操作数据库的抽象访问接口
    func Open(driverName, dataSourceName string) (*DB, error)

  • sql.Open并不会立即建立一个数据库的网络连接, 也不会对数据库链接参数的合法性做检验, 它仅仅是初始化一个sql.DB对象. 当真正进行第一次数据库查询操作时, 此时才会真正建立网络连接;

  • sql.DB表示操作数据库的抽象接口的对象,但不是所谓的数据库连接对象,sql.DB对象只有当需要使用时才会创建连接,如果想立即验证连接,需要用Ping()方法;

  • sql.Open返回的sql.DB对象是协程并发安全的.

  • sql.DB的设计就是用来作为长连接使用的。不要频繁Open, Close。比较好的做法是,为每个不同的datastore建一个DB对象,保持这些对象Open。如果需要短连接,那么把DB作为参数传入function,而不要在function中Open, Close。

3、数据库的基本操作

  • 1.调用 db.Query 执行 SQL 语句, 此方法会返回一个 Rows 作为查询的结果
  • 2.通过 rows.Next() 迭代查询数据.
  • 3.通过 rows.Scan() 读取每一行的值
  • 4.调用 db.Close() 关闭查询
func (dbw *DbWorker) QueryData() {
	dbw.QueryDataPre()
	rows, err := dbw.Db.Query(`SELECT * From user where age >= 20 AND age < 30`)
	defer rows.Close()
	if err != nil {
		fmt.Printf("insert data error: %v\n", err)
		return
	}
	for rows.Next() {
		rows.Scan(&dbw.UserInfo.Id, &dbw.UserInfo.Name, &dbw.UserInfo.Age)
		if err != nil {
			fmt.Printf(err.Error())
			continue
		}
		if !dbw.UserInfo.Name.Valid {
			dbw.UserInfo.Name.String = ""
		}
		if !dbw.UserInfo.Age.Valid {
			dbw.UserInfo.Age.Int64 = 0
		}
		fmt.Println("get data, id: ", dbw.UserInfo.Id, " name: ", dbw.UserInfo.Name.String, " age: ", int(dbw.UserInfo.Age.Int64))
	}

	err = rows.Err()
	if err != nil {
		fmt.Printf(err.Error())
	}
}

注意

  • select 查询的字段,需要按照此顺序 rows.Scan(&id, &name, &age)赋值, 不然会造成数据读取的错位
  • golang是强类型语言,注意类型
  • 每次db.Query操作后, 都建议调用rows.Close(),rows.Close()操作是幂等操作,即一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同, 所以即便对已关闭的rows再执行close()也没关系

4.单行查询 QueryRow,不需要手动关闭连接

var name string
err = db.QueryRow("select name from user where id = ?", 1).Scan(&name)
if err != nil {
    log.Fatal(err)
}
fmt.Println(name)

5.通过db.Exec()插入数据,通过返回的err可知插入失败的原因,

通过返回的ret可以进一步查询本次插入数据影响的行数RowsAffected和LastInsertId最后插入的Id(如果数据库支持查询最后插入Id)

func (dbw *DbWorker) insertData() {
	ret, err := dbw.Db.Exec(`INSERT INTO user (name, age) VALUES ("xys", 23)`)
	if err != nil {
		fmt.Printf("insert data error: %v\n", err)
		return
	}
	if LastInsertId, err := ret.LastInsertId(); nil == err {
		fmt.Println("LastInsertId:", LastInsertId)
	}
	if RowsAffected, err := ret.RowsAffected(); nil == err {
		fmt.Println("RowsAffected:", RowsAffected)
	}
}

6.预编译语句(Prepared Statement)

预编译语句所提供的功能:

  • PreparedStatement 可以实现自定义参数的查询,一次编译,多次执行,从而提高性能
  • PreparedStatement 通常来说, 比手动拼接字符串 SQL 语句高效.
  • PreparedStatement 可以防止SQL注入攻击

db.Prepare()返回的statement使用完之后需要手动关闭,即defer stmt.Close()

package main

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

type DbWorker struct {
	Dsn      string
	Db       *sql.DB
	UserInfo userTB
}
type userTB struct {
	Id   int
	Name sql.NullString
	Age  sql.NullInt64
}

func main() {
	var err error
	dbw := DbWorker{
		Dsn: "root:123456@tcp(localhost:3306)/sqlx_db?charset=utf8mb4",
	}
	dbw.Db, err = sql.Open("mysql", dbw.Dsn)
	if err != nil {
		panic(err)
		return
	}
	defer dbw.Db.Close()

	dbw.insertData()
	dbw.queryData()
}

func (dbw *DbWorker) insertData() {
	stmt, _ := dbw.Db.Prepare(`INSERT INTO user (name, age) VALUES (?, ?)`)
	defer stmt.Close()

	ret, err := stmt.Exec("xys", 23)
	if err != nil {
		fmt.Printf("insert data error: %v\n", err)
		return
	}
	if LastInsertId, err := ret.LastInsertId(); nil == err {
		fmt.Println("LastInsertId:", LastInsertId)
	}
	if RowsAffected, err := ret.RowsAffected(); nil == err {
		fmt.Println("RowsAffected:", RowsAffected)
	}
}

func (dbw *DbWorker) QueryDataPre() {
	dbw.UserInfo = userTB{}
}
func (dbw *DbWorker) queryData() {
	stmt, _ := dbw.Db.Prepare(`SELECT * From user where age >= ? AND age < ?`)
	defer stmt.Close()

	dbw.QueryDataPre()

	rows, err := stmt.Query(20, 30)
	defer rows.Close()
	if err != nil {
		fmt.Printf("insert data error: %v\n", err)
		return
	}
	for rows.Next() {
		rows.Scan(&dbw.UserInfo.Id, &dbw.UserInfo.Name, &dbw.UserInfo.Age)
		if err != nil {
			fmt.Printf(err.Error())
			continue
		}
		if !dbw.UserInfo.Name.Valid {
			dbw.UserInfo.Name.String = ""
		}
		if !dbw.UserInfo.Age.Valid {
			dbw.UserInfo.Age.Int64 = 0
		}
		fmt.Println("get data, id: ", dbw.UserInfo.Id, " name: ", dbw.UserInfo.Name.String, " age: ", int(dbw.UserInfo.Age.Int64))
	}

	err = rows.Err()
	if err != nil {
		fmt.Printf(err.Error())
	}
}

二、redis

1.熟悉redis命令

redis支持五种数据结构
键:字符串         key:value
键:哈希             key:{"name":"xiaoming"}
键:列表             key:[1,2,3,4,5]
键:集合             key:(1,2,3,4,5)
键:有序集合      key:(1 a,2 b,3 c)        有序集合中,每个元素,都有一个值表示序号,即顺序



keys *       查看当前redis数据库,有哪些键
del set1        删除set1这个键、

第一种:   键:字符串
set name xiaoming    				添加一个字符串的键值对
get name             				 	 获取name键的值
setex hello 10 world  				设置键为hello,值为world,10秒后过期
mset name "xiaoli" age 20 sex girl   		                一次设置多个键值
mget sex age name                                                  	获取多个键的值

修改键和新增键都是同一个操作
set  sex  boy
incr age                                       让age的值加1.  age的值必须是数字类型
decr age                                       让age减1
incrby age 10				       让age加10
decrby age 10				       让age减10
append name "你好"			       在name键的这唯一值后面追加
strlen name				       求name键,值的长度

增加:set   修改:set    查询:get     删除:del


第二种:键:哈希                key:{"name":"xiaoming"}
hset hash1 name "lily"       往redis添加一个键值对.键为hash1,值为{"name":"lily"}   =>  hash1:{"name":"lily"}
hset hash1 money 100         在往hash1中,添加一个键值对   		     =>hash1:{"name":"lily","money":100}
hmset hash2 age 20 money 1000 往hash2中加入多个键值对

hkeys hash1               获取hash1中所有的键值  即:name,money
hget  hash1 money         获取hash1中指定键的值.   即:hash1.money
hmget hash2 age money name  获取hash2中多个键的值. 即:hash2.age   hash2.money  hash2.name
hgetall hash2		    获取hash2中所有的键值对.  两两一组
hvals hash2	            获取hash2中所有键值对的值.
hlen hash2		    获取hash2中键值对的个数.

hexists hash2 name	    查看name键在不在hash2中.在返回1,不在返回0
hdel hash2 name		    删除hash2中的一个键值对


第三种:      键:列表             key:[1,2,3,4,5]
lpush list1 aaa             向列表list1,左侧插入元素aaa    list1:[aaa]
lpush list1 bbb	            list1:[bbb,aaa]
lpush list1 ccc             list1:[ccc,bbb,aaa]

lrange list1 0 100          查看list1列表中,第0到100个元素.

rpush list1 1111            向列表list1尾部插入元素  list1:[ccc,bbb,aaa,1111]
rpush list1 2222	    list1:[ccc,bbb,aaa,1111,2222]
rpush list1 3333	    list1:[ccc,bbb,aaa,1111,2222,3333]

linsert list1 after bbb  zzz    在list1列表中,bbb的后面插入zzz
linsert list1 before bbb  000   在list1列表中,bbb的前面插入000
 
lset list1 0 fff                设置list1第0个元素的值为fff
lset list1 -2 9999		设置list1倒数第2个元素的值为9999

lpop list1                      从list1头部删除,并返回一个元素
rpop list1                      从list1尾部删除,并返回一个元素

lindex list1 4                  获取list1第4个元素,不会删除
lrem list1 -2 ccc            从尾到头开始检索,删除2个ccc. 数字代表方向和个数. -1:代表从尾到头删一个. 2:从头到尾删2个

ltrim list1 0 2               裁剪列表1,只留下第0到第2个元素.其余的元素丢弃
llen list1		      返回list1的长度

第四种:  键:集合             key:(1,2,3,4,5)
sadd set1 hello              向集合set1中添加元素,自动去重   =>  set1:(hello)
smembers set1		     获取集合set1中所有元素
scard set1		     返回集合set1中元素的个数

sinter set1 set2             返回set1和set2相同(可连的)的元素.即求交集
sdiff  set1 set2             求set1和set2不同的元素.因为set1在前,所以返回set1有,set2没有的元素.即求差集.
			     sdiff  先除去相同的元素,再求第一个集合有,第二个集合没有的元素.

sismember set1 hello         判断hello在不在set1中.  在返回1,不在返回0
 
  
第五种:  键:有序集合         key:(1 a,2 b,3 c)				
zadd z1 1 a 3 b 4 c 5 d 2 e     为有序集合z1添加元素.元素有序号.   a的序号是1   b的序号是3
zrange z1 0 100                 查看有序集合z1的所有元素. 元素会按照序号排列
zcard z1			返回z1中元素的个数
zcount z1 1 3			返回z1中,序号1到序号3之间元素的个数
zscore z1 e			返回z1中,元素e的分数(序号)

2.redisgo 方法偏原生

1. 基本操作

import "github.com/garyburd/redigo/redis"

func main()  {
    conn,err := redis.Dial("tcp","10.1.210.69:6379")
    if err != nil {
        fmt.Println("connect redis error :",err)
        return
    }
    defer conn.Close()
}
  • 可以使用Dial,DialWithTimeout或者NewConn函数来创建连接,当任务完成时,应用程序必须调用Close函数来完成操作。

  • Conn接口中的do方法执行redis命令

 _, err = conn.Do("SET", "name", "wd")
    if err != nil {
        fmt.Println("redis set error:", err)
    }
    name, err := redis.String(conn.Do("GET", "name"))
    if err != nil {
        fmt.Println("redis get error:", err)
    } else {
        fmt.Printf("Got name: %s \n", name)
    }

2.Pipelining(管道)

  • 管道操作可以理解为并发操作,并通过Send(),Flush(),Receive()三个方法实现。使用send()方法一次性向服务器发送一个或多个命令,命令发送完毕时,使用flush()方法将缓冲区的命令输入一次性发送到服务器,客户端再使用Receive()方法依次按照先进先出的顺序读取所有命令操作结果。
Send(commandName string, args ...interface{}) error
Flush() error
Receive() (reply interface{}, err error)
 Send:发送命令至缓冲区
 Flush:清空缓冲区,将命令一次性发送至服务器
 Recevie:依次读取服务器响应结果,当读取的命令未响应时,该操作会阻塞。
 
 
 func main()  {
    conn,err := redis.Dial("tcp","10.1.210.69:6379")
    if err != nil {
        fmt.Println("connect redis error :",err)
        return
    }
    defer conn.Close()
    conn.Send("HSET", "student","name", "wd","age","22")
    conn.Send("HSET", "student","Score","100")
    conn.Send("HGET", "student","age")
    conn.Flush()

    res1, err := conn.Receive()
    fmt.Printf("Receive res1:%v \n", res1)
    res2, err := conn.Receive()
    fmt.Printf("Receive res2:%v\n",res2)
    res3, err := conn.Receive()
    fmt.Printf("Receive res3:%s\n",res3)
}

3.发布订阅

func Subs() {  //订阅者
    conn, err := redis.Dial("tcp", "10.1.210.69:6379")
    if err != nil {
        fmt.Println("connect redis error :", err)
        return
    }
    defer conn.Close()
    psc := redis.PubSubConn{conn}
    psc.Subscribe("channel1") //订阅channel1频道
    for {
        switch v := psc.Receive().(type) {
        case redis.Message:
            fmt.Printf("%s: message: %s\n", v.Channel, v.Data)
        case redis.Subscription:
            fmt.Printf("%s: %s %d\n", v.Channel, v.Kind, v.Count)
        case error:
            fmt.Println(v)
            return
        }
    }
}

func Push(message string)  { //发布者
    conn, _ := redis.Dial("tcp", "10.1.210.69:6379")
    _,err1 := conn.Do("PUBLISH", "channel1", message)
       if err1 != nil {
             fmt.Println("pub err: ", err1)
                 return
            }

}

func main()  {
    go Subs()
    go Push("this is wd")
    time.Sleep(time.Second*3)
}

4.事务

MULTI, EXEC,DISCARD和WATCH是构成Redis事务的基础,当然我们使用go语言对redis进行事务操作的时候本质也是使用这些命令。

MULTI:开启事务

EXEC:执行事务

DISCARD:取消事务

WATCH:监视事务中的键变化,一旦有改变则取消事务。

func main()  {
    conn,err := redis.Dial("tcp","10.1.210.69:6379")
    if err != nil {
        fmt.Println("connect redis error :",err)
        return
    }
    defer conn.Close()
    conn.Send("MULTI")
    conn.Send("INCR", "foo")
    conn.Send("INCR", "bar")
    r, err := conn.Do("EXEC")
    fmt.Println(r)
}
//[1, 1]

3.go_redis 操作,方法封装

连接

//连接服务器
    redisdb = redis.NewClient(&redis.Options{
        Addr:     "localhost:6379", // use default Addr
        Password: "",               // no password set
        DB:       0,                // use default DB
    })

    //心跳
    pong, err := redisdb.Ping().Result()
    log.Println(pong, err) // Output: PONG <nil>
  • 通过连接接口,直接调用命令方法
err := redisdb.Set("key", "value", 1*time.Second).Err()
val2, err := redisdb.Get("missing_key").Result()
posted @ 2020-08-06 17:40  fanzou  阅读(408)  评论(0编辑  收藏  举报