mysql支持插件式的存储引擎, 常见的存储引擎有:MyISAM,InnoDB
MyISAM:
查询速度快
只支持表锁
不支持事务
InnoDB:
事务:多个SQL操作为一个整体执行
事务特点:ACID
索引:
原理:B树和B+树
需要掌握的知识点: 索引类型 命中 分库分表 SQL注入 SQL慢优化查询 MYSQL主从 读写分离
原生支持连接池,是并发安全的
该标准库没有具体实现,只列出第三方库需要实现的具体内容
go get github.com/go-sql-driver/mysql
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
// 连接数据库
dsn:="root:【填入自己的数据库密码】@tcp(127.0.0.1:3306)/hello"
// 连接数据库
db,err:=sql.Open("mysql",dsn)// 不会校验用户和密码石是否正确,只会校验格式是否正确
if err != nil { // dsn格式不正确会报错
fmt.Printf(" %s invalid, err:%v\n",dsn,err)
return
}
err=db.Ping()
if err != nil {
fmt.Printf("Open %s failed, err:%v\n",dsn,err)
return
}
db.SetMaxOpenConns(10)// 最大连接数
db.SetMaxIdleConns(3)// 最大空闲连接数
fmt.Println("连接数据库成功!")
}
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
var db *sql.DB //一个连接池
type user struct{
id int `db:"id"`
name string `db:"name"`
age int `db:"age"`
}
func initDB()(err error) {
// 连接数据库
dsn:="root:6862273@tcp(127.0.0.1:3306)/hello"
// 连接数据库
db,err=sql.Open("mysql",dsn)// 不会校验用户和密码石是否正确,只会校验格式是否正确
if err != nil { // dsn格式不正确会报错
//fmt.Printf(" %s invalid, err:%v\n",dsn,err)
return
}
err=db.Ping()
if err != nil {
//fmt.Printf("Open %s failed, err:%v\n",dsn,err)
return
}
// fmt.Println("连接数据库成功!")
db.SetMaxOpenConns(10)// 最大连接数
db.SetMaxIdleConns(3)// 最大空闲连接数
return
}
func queryone(id int)(u *user){
u=&user{}
// 1. 查询单条记录的sql语句
sqlstr:="select id,name,age from user where id=?;"
// 2.执行
db.QueryRow(sqlstr,id).Scan(&u.id,&u.name,&u.age) // 从连接池取取一个连接出来,去数据库查询单条记录,并调用scan拿结果
// 3 返回结果
return u
}
func queryMore(n int){
// 1. sql语句
sqlstr:="select id,name,age from user where id > ?"
// 2 执行
rows, err := db.Query(sqlstr, n)
if err!=nil{
fmt.Printf(" exec %s query faile, err := %v",sqlstr,err)
return
}
// 3 关闭数据库连接
defer rows.Close()
// 循环取数
for rows.Next(){
var u1 user
err:=rows.Scan(&u1.id,&u1.name,&u1.age)
if err != nil{
fmt.Printf("scan failed, err : %v\n",err)
}
fmt.Printf("user:%#v\n",u1)
}
}
// 插入数据
func insert(name string,age int){
// 1写sql语句
sqlstr:="insert into user(name,age) values(?,?)"
// 2 执行exec
ret,err:=db.Exec(sqlstr,name,age)
if err != nil {
fmt.Printf("insert failed, err : %v\n",err)
return
}
// 插入数据的操作,会拿到操作数据的id值
id,err:=ret.LastInsertId()
if err != nil {
fmt.Printf("get id failed, err : %v\n",err)
return
}
fmt.Println("id",id)
}
// 更新数据
func updateRow(newAge int,id int){
sqlstr:="update user set age = ? where id =?"
ret,err:=db.Exec(sqlstr,newAge,id)
if err != nil {
fmt.Printf("update failed, err : %v\n",err)
return
}
n,err:=ret.RowsAffected()
if err != nil {
fmt.Printf("get id failed, err : %v\n",err)
return
}
fmt.Printf("更新了%d行数据\n",n)
}
// 删除数据
func deleteRow(id int){
sqlstr:="delete from user where id=?"
ret,err := db.Exec(sqlstr,id)
if err != nil {
fmt.Printf("delete failed, err : %v\n",err)
return
}
n,err:=ret.RowsAffected()
if err != nil {
fmt.Printf("get id failed, err : %v\n",err)
return
}
fmt.Printf("删除了%d行数据\n",n)
}
func main() {
err:=initDB()
if err != nil {
fmt.Printf("init DB failed, err : %v\n", err)
}
fmt.Println("连接数据库成功!")
//insert("wangkai",400)
//queryone(2)
//queryMore(3)
updateRow(34,2)
//deleteRow(2)
}
普通SQL语句执行过程:
预处理执行过程:
优点:
适用于批量处理类的操作。
Go 实现mysql预处理:
func (db *DB) Prepare(query string) (*Stmt, error)
Prepare
方法会先将sql语句发送给MySQL服务端,返回一个准备好的状态用于之后的查询和命令。返回值可以同时执行多个查询和命令。
// 预处理方式插入多条数据
func prepareInsert(){
sqlstr:="insert into user(name,age) values(?,?)"
stmt,err := db.Prepare(sqlstr)
if err != nil {
fmt.Printf("prepar failed, err : %v\n",err)
return
}
defer stmt.Close()
// 后续只需要stmt 执行操作
var m= map[string]int{
"dazhangwei":30,
"lushuo":230,
"zhangerwei":46,
}
for k,v := range m {
stmt.Exec(k,v)
}
}
事务:一个最小的不可再分的工作单元;通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元),同时这个完整的业务需要执行多次的DML(insert、update、delete)语句共同联合完成。A转账给B,这里面就需要执行两次update操作。
在MySQL中只有使用了Innodb
数据库引擎的数据库或表才支持事务。事务处理可以用来维护数据库的完整性,保证成批的SQL语句要么全部执行,要么全部不执行。
通常事务必须满足4个条件(ACID):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。
条件 | 解释 |
---|---|
原子性 | 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。 |
一致性 | 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。 |
隔离性 | 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。 |
持久性 | 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。 |
Go语言中使用以下三个方法实现MySQL中的事务操作。
开始事务
func (db *DB) Begin() (*Tx, error)
提交事务
func (tx *Tx) Commit() error
回滚事务
func (tx *Tx) Rollback() error
func transaction(){
// 开启事务
tx,err := db.Begin()
if err != nil {
fmt.Printf("Transaction Begin failed, err: %v\n",err)
return
}
// 执行多个sql操作
sqlstr1 := "update user set age=age-2 where id=1"
sqlstr2:="update user set age=age+2 where id=2"
ret,err:=tx.Exec(sqlstr1)
if err != nil{
// 要回滚
tx.Rollback()
fmt.Println("执行sql1出错,要回滚")
}
if rows,_:=ret.RowsAffected();rows==0{
tx.Rollback()
fmt.Println("执行sql1出错,要回滚")
}
ret,err=tx.Exec(sqlstr2)
if err != nil{
// 要回滚
tx.Rollback()
fmt.Println("执行sql2出错,要回滚")
}
if rows,_:=ret.RowsAffected();rows==0{
tx.Rollback()
fmt.Println("执行sql2出错,要回滚")
}
// 上面都执行成功,提交
err=tx.Commit()
if err != nil {
tx.Rollback()
fmt.Println("提交出错,要回滚")
}
fmt.Println("事务执行成功!")
}
sqlx是一个go语言包,在内置database/sql包之上增加了很多扩展,简化数据库操作代码的书写
sqlx设计和database/sql使用方法是一样的。包含有4种主要的handle types:
所有的handler types都提供了对database/sql的兼容,意味着当用调用sqlx.DB.Query时,可以直接替换为sql.DB.Query。这就使得sqlx可以很容易的加入到已有的数据库项目中。
此外,sqlx还有两个cursor类型:
func initDB() (err error) {
// 连接数据库
dsn := "root:[你的数据库密码]@tcp(127.0.0.1:3306)/hello"
// 连接数据库
db, err = sqlx.Open("mysql", dsn) // 不会校验用户和密码石是否正确,只会校验格式是否正确
if err != nil { // dsn格式不正确会报错
//fmt.Printf(" %s invalid, err:%v\n",dsn,err)
return
}
err = db.Ping()
if err != nil {
//fmt.Printf("Open %s failed, err:%v\n",dsn,err)
return
}
// fmt.Println("连接数据库成功!")
db.SetMaxOpenConns(10) // 最大连接数
db.SetMaxIdleConns(3) // 最大空闲连接数
return
}
sqlx:
func initDB() (err error) {
// 连接数据库
dsn := "root:[你的数据库密码]@tcp(127.0.0.1:3306)/hello"
// 连接数据库
db, err = sqlx.Connect("mysql",dsn) //
if err != nil {
//fmt.Printf("connect failed, err : %v\n",err)
return
}
// fmt.Println("连接数据库成功!")
db.SetMaxOpenConns(10) // 最大连接数
db.SetMaxIdleConns(3) // 最大空闲连接数
return
}
主要区别是connect 相当于sqlx的open和ping的结合,下面是connect的代码:
// Connect to a database and verify with a ping.
func Connect(driverName, dataSourceName string) (*DB, error) {
db, err := Open(driverName, dataSourceName)
if err != nil {
return nil, err
}
err = db.Ping()
if err != nil {
db.Close()
return nil, err
}
return db, nil
}
sqlx中的handle types实现了数据库查询相同的基本的操作语法。
对内置语法的扩展
扩展下面新的语法
因为官方的sql包query()和quertrow()在将查询到的数据传递到变量时,对于结构体变量,只能通过scan()方法逐一传递。sqlx包扩展了structscan()方法。可以对结果体变量直接传值。
例子:
type Place struct {
Country string
City sql.NullString
TelephoneCode int `db:"telcode"`
}
// 多条查询
rows, err := db.Queryx("SELECT * FROM place")
for rows.Next() {
var p Place
err = rows.StructScan(&p)
}
// 单条查询
var p Place
err := db.QueryRowx("SELECT city, telcode FROM place LIMIT 1").StructScan(&p)
他们是上述查询(query)和传值扫描(scan)操作的集合。
可扫描的定义:
string
, int
sql.Scanner
time.Time
)get和select使用 rows.Scan
方法扫描可扫描的数据, rows.StructScan
扫描不可扫描的数据。get用于查询单条数据,select用于查询多条数据。
例子:
p := Place{}
pp := []Place{}
// this will pull the first place directly into p
err = db.Get(&p, "SELECT * FROM place LIMIT 1")
// this will pull places with telcode > 50 into the slice pp
err = db.Select(&pp, "SELECT * FROM place WHERE telcode > ?", 50)
// they work with regular types as well
var id int
err = db.Get(&id, "SELECT count(*) FROM place")
// fetch at most 10 place names
var names []string
err = db.Select(&names, "SELECT name FROM place LIMIT 10")
Get 和 Select 会关闭rows ,无需再调用rows.close()方法。
但是要注意的是,select会把查询结果一次性全放到内存,如果查询量较大的话,可以使用queryx和stuctscan结合的方法。
func Select(q Queryer, dest interface{}, query string, args ...interface{}) error {
rows, err := q.Queryx(query, args...)
if err != nil {
return err
}
// if something happens here, we want to make sure the rows are Closed
defer rows.Close()
return scanAll(rows, dest, false)
}
没啥区别
Preparex和get方法可用于预处理
stmt, err := db.Preparex(`SELECT * FROM place WHERE telcode=?`)
var p Place
err = stmt.Get(&p, 852)
因为database / sql不会检查您的查询,而是将您的参数直接传递给驱动程序,所以使用IN子句处理查询变得困难。
SELECT * FROM users WHERE level IN (?);
?只会接受一个单变量,但是更希望它接受一个可变数组。
var levels = []int{4, 6, 7}
rows, err := db.Query("SELECT * FROM users WHERE level IN (?);", levels)
可以先使用 sqlx.In
语法构建查询语句和参数列表。
var levels = []int{4, 6, 7}
query, args, err := sqlx.In("SELECT * FROM users WHERE level IN (?);", levels)
// sqlx.In returns queries with the `?` bindvar, we can rebind it for our backend
query = db.Rebind(query)
rows, err := db.Query(query, args...)
例子:
sqlstr, args, err := sqlx.In("select id,category_name,category_no from category where id in (?)", categoryIds)
if err != nil {
return
}
// 查询
err = DB.Select(&categoryList, sqlstr, args...)
##遇到的问题及解决方案
Q:go的time包的time.time类型与 mysql的date datetime 自动解析?
A:在dsn地址加上parseTime=true字段
Q:时差问题?
A:loc=local
package main
import (
"database/sql"
"fmt"
"time"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql",
"root:6862273aliyun@tcp(39.97.229.151:3306)/weixiaobo?parseTime=true&loc=Local")
var myTime time.Time
rows, err := db.Query("SELECT current_timestamp()")
fmt.Println(time.Now())
if rows.Next() {
if err = rows.Scan(&myTime); err != nil {
panic(err)
}
}
fmt.Println(myTime)
}