# mysql
- 涉及概念默认的InnoDB引擎基于B+树的存储方式(索引和数据存在一起)
- 聚簇索引会设为主键,(每主键,最左原则上,没最左索引,则隐含使用rowid 6bit)
- 数据存储在b+树上按照主键顺序存放。
- 使用自增每次插入就直接最后添加(一页满了16k,就添加一块)
# mysql相关概念
# golang操作mysql
- golang 针对 sql数据库做了框架,希望开发这按照官方的框架接口来时先数据驱动,实现不同数据库使用相同代码的无缝奇幻
database/sql包定义了SQL或类SQL数据库的泛用接口- 官方库里面并没有实现任何数据库的驱动
- 使用数据库至少注入一种数据库驱动
# 安装mysql数据库驱动
go get -u github.com/go-sql-driver/mysql
1
# 使用mysql数据库驱动
import (
"database/sql"
_ "github.com/go-sql-driver/mysql" // 注入驱动,匿名导入即可
)
func main() {
// DSN:Data Source Name
dsn := "user:password@tcp(127.0.0.1:3306)/dbname"
// 使用sql.Open函数验证参数格式,并未实际创建连接
db, err := sql.Open("mysql", dsn)
if err != nil {
panic(err)
}
defer db.Close()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 初始化 连接池 连接池
- sql.Open函数
- 验证参数格式,并未实际创建连接,可以调用Ping方法验证连接
- 返回的db对象线程安全
- db对象已经实现了连接池,open函数仅需要调用一次
- 很少需要关闭得到的 db 对象
- 在 init 函数中创建初始化
// init db
var db *sql.DB
func initDB() error {
dsn := "user:password@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
// db 和 err 不适用 := 方法,因为 db 为全局变量
var err error
db, err = sql.Open("mysql", dsn)
if err != nil {
return err
}
err = db.Ping()
if err != nil {
return err
}
return nil
}
func main() {
err := initDB() // 调用输出化数据库的函数
if err != nil {
fmt.Printf("init db failed,err:%v\n", err)
return
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 设置最大连接数
// n 小于 0 不限制连接数,
func (db *DB) SetMaxOpenConns(n int)
1
2
2
# 最大闲置连接数
- 有些连接使用完毕了不一定直接关掉,可以让其空闲,这里设置最大空闲数量
// 如果n<=0,不会保留闲置连接
func (db *DB) SetMaxIdleConns(n int)
1
2
2
# golang增删改查(CRUD)mysql
# 首先创建数据库
CREATE DATABASE sql_test;
use sql_test;
CREATE TABLE `user` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(20) DEFAULT '',
`age` INT(11) DEFAULT '0',
PRIMARY KEY(`id`)
)ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 查
定义一个结构体保存查询的数据
type user struct {
id int
age int
name string
}
1
2
3
4
5
2
3
4
5
# 单行查询
db.QueryRow()- 该函数不会反回 error
- 调用该函数必须调用Scan()方法,不然不会释放连接,推荐使用链式调用
// 函数签名
func (db *DB) QueryRow(query string, args ...interface{}) *Row
1
2
2
// 实例
func qureyrow() {
s = "select id, name, age from user where id=?"
var u user
err := db.QueryRow(s, 1).Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
}
......
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 多行查询
db.Query()- 反回的 rows 对象必须关闭来关闭连接
- 使用
for rows.Next() {rows.Scan()}遍历每一行数据
func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
1
使用案例
func qurey(){
sqlStr := "select id, name, age from user where id > ?"
rows, err := db.Query(sqlStr, 0)
if err != nil {
fmt.Printf("query failed, err:%v\n", err)
return
}
// 非常重要:关闭rows释放持有的数据库链接
defer rows.Close()
// 循环读取结果集中的数据
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.name, &u.age)
if err != nil {
fmt.Printf("scan failed, err:%v\n", err)
return
}
fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 曾删改
- 都使用
db.Exec()- 反悔的 Result 对象
- ret.LastInsertId() 最后插入行id
- ret.RowsAffected() 受影响的行数
- 反悔的 Result 对象
// 函数签名
func (db *DB) Exec(query string, args ...interface{}) (Result, error)
1
2
2
// 插入数据
func insertRowDemo() {
sqlStr := "insert into user(name, age) values (?,?)"
ret, err := db.Exec(sqlStr, "王五", 38)
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
theID, err := ret.LastInsertId() // 新插入数据的id
if err != nil {
fmt.Printf("get lastinsert ID failed, err:%v\n", err)
return
}
fmt.Printf("insert success, the id is %d.\n", theID)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# mysql预处理
# 什么是mysql预处理
普通的没有预处理的sql语句执行过程
- 客户端进行站位符替换得到完整sql
- 发送完整sql给mysql
- mysql执行完返回
预处理过程
- sql分成两部分, 命令和数据
- 先发送出命令部分,mysql服务器对其进行sql预处理
- 数据部分发送给mysql,有服务器完成占位符替换操作
- mysql 服务端执行完整sql语句将结果返回客户端
# 优势
- 让服务端重复执行的sql,sql语句也是要编译后才运行的,
- 预处理的好处就是,一次编译多次运行,提高性能
- 可以避免sql注入的问题
# golang实现sql预处理查询
func (db *DB) Prepre(query string) (*Stmt, error)
1
- 使用该函数创建的状态 stmt,使用这个可以同时执行多个查询命令。
func prepareQuery() {
sql := "select id, name, age form user where id > ?"
stmt, err := db.Prepare(sql)
if err != nil {
return
}
defer stmt.Close()
rows, err := stmt.Query(0)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
var u user
err := rows.Scan(&u.id, &u.name, &u.age)
if err != nil {
return
}
fmt.Printf(">>>%#v", u)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 使用预处理的-增删改
// 预处理插入示例
func prepareInsertDemo() {
sqlStr := "insert into user(name, age) values (?,?)"
stmt, err := db.Prepare(sqlStr)
if err != nil {
fmt.Printf("prepare failed, err:%v\n", err)
return
}
defer stmt.Close()
_, err = stmt.Exec("小王子", 18)
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
_, err = stmt.Exec("沙河娜扎", 18)
if err != nil {
fmt.Printf("insert failed, err:%v\n", err)
return
}
fmt.Println("insert success.")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Sql注入问题
DANGER
任何时候都不要使用自行拼接字符串的方式组合SQL语句
- 存在sql注入问题的语句
func Demo(name) {
sqlStr := fmt.Sprintf("select id, name, age from user where name='%s'", name)
var u user
err := db.QueryRow(sqlStr).Scan(&u.id, &u.name, &u.age)
......
}
1
2
3
4
5
6
2
3
4
5
6
- 下面的语句就会出现注入问题
Demo("xxx' or 1=1#")
Demo("xxx' union select * from user #")
Demo("xxx' and (select count(*) from user) <10 #")
1
2
3
2
3
# golang实现mysql的事务
- 事务:一个最小的不可再分的工作单元
- 比如银行转账,需要同时更新两个账户的余额
# 事务的ACID
- acid:原子性、一致性、隔离性、持久性
- mysql仅Inodb引擎支持事务 |特性|解释| |-|-| |原子性|事务的执行要全成功,要么全失败,事务可以通过回滚恢复| |一致性|事务执行前后,数据库的完整性没有破坏| |隔离性|允许多个事务并发对数据的读写,分级(读未提交、读提交、可重复度、串行化)| |持久性|事务执行完就已经落盘,不会因为机器故障就导致丢失|
# golang实现事务的三个方法
- 分别是,开始事务、提交事务、回滚事务
func (db *DB) Begin() (*Tx, error)
func (tx *Tx) Commit() error
func (tx *Tx) Rollback() error
1
2
3
2
3
- 事物操作能够确保两次更新操作要么同时成功要么同时失败,不会存在中间状态。
func shiwu() {
tx, err := db.Begin() // 开启事务
if err != nil {
if tx != nil {
tx.Rollbak() // 回滚
}
fmt.Print("err")
return
}
sqlStr1 := "Update user set age=30 where id=?"
ret1, err := tx.Exec(sqlStr1, 2)
if err != nil {
tx.Rollbak()
fmt.Print("err")
return
}
affRow1, err := ret1.RowsAffected()
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err)
return
}
sqlStr2 := "Update user set age=40 where id=?"
ret2, err := tx.Exec(sqlStr2, 3)
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec sql2 failed, err:%v\n", err)
return
}
affRow2, err := ret2.RowsAffected()
if err != nil {
tx.Rollback() // 回滚
fmt.Printf("exec ret1.RowsAffected() failed, err:%v\n", err)
return
}
fmt.Println(affRow1, affRow2)
if affRow1 == 1 && affRow2 == 1 {
fmt.Println("事务提交啦...")
tx.Commit() // 提交事务
} else {
tx.Rollback()
fmt.Println("事务回滚啦...")
}
fmt.Println("exec trans success!")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45