这是我参与「第五届青训营 」伴学笔记创作活动的第 7 天
database/sql 包
基本用法
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:pwd@tcp(127.0.0.1:3306)/hello")
if err != nil {
panic(err)
}
rows, err := db.Query("select id, name from users where id = ?", 1)
if err != nil {
panic(err)
}
defer rows.Close()
var users []User
for rows.Next() {
var user User
err := rows.Scan(&user.ID, &user.Name)
if err != nil {
print(err)
}
users = append(users, user)
}
if rows.Err() != nil {
}
}
- 导入mysql driver实现, 使用 driver + DSN(data source name) 初始化DB连接
db.Query执行查询, 获得返回的cursor rows, 并利用defer关闭连接- 遍历cursor拉取数据
- 拉取cursor.Err, 判断数据库操作中是否发生错误
设计原理
database/sql 作为应用程序与数据库的中间层, 为go程序提供了标准化的数据库连接, 操作接口, 并定义了数据库的连接, 操作接口, 数据库通过实现接口来提供连接能力. 并且自身维护了数据库的连接池, 用以复用连接.
数据库连接接口
type Driver interface {
Open(name string) (Conn, error)
}
通过模块的init函数注册, 但此接口有两个问题,
- 通过名称标识数据库Driver, 多个同数据库的Driver实现会相互冲突
Open操作只支持字符串, 无法语义化表达连接参数, 甚至无法设置某些特定参数 因此提出了接口版本2
type Connector interface {
Connect(context.Context) (Conn, error)
Driver() Driver
}
func OpenDB(c driver.Connector) *DB {
//...
}
只在打开数据库时使用到了Connector, 各种数据库的Driver实现可以自定义自己的结构体, 只需要提供Connect函数即可.
数据库的连接可以分为如下几种
- 直连连接/Conn,
- 预编译/Stmt, 会将要执行的sql指令在数据库端编译, 客户端选择指令编号执行
- 事务/Tx, 连接会开启事务, 保证在连接期间sql执行的一致性.
gorm
orm(object relation mapping), 它是一类框架, 用于提供以操作编程语言对象的方式来操作对应的数据库实体与关系. 可以屏蔽不同数据库的差异和sql语句.
gorm是go语言下一款出色的orm框架, 它的功能完善
- 关联: 一对一, 一对多, 单表自关联, 多态; Preload, Joins 预加载, 级联删除; 关联模式; 自定义关联表
- 事务: 事务代码块, 嵌套事务, Save Point
- 多数据库, 读写分离, 命名参数, Map, 子查询, 分组条件, 代码共享, SQL表达式(查询, 创建, 更新), 自动选自动, 查询优化器
- 字段权限, 软删除, 批量数据处理, Prepared Stmt, 自定义类型, 命名策略, 虚拟字段, 自动track时间, SQL builder, Logger
- 代码生成, 复合主键, Constraint, Prometheus, Auto Migratio, 跨数据兼容 等等功能, 不仅为我们提供了CRUD接口, 也为操作数据库的方方面面提供保障
基本用法如下
db, err := gorm.Open(
mysql.Open("user:pwd@tcp(127.0.0.1:3306)/hello")
)
var users []User
err = db.Select("id", "name").Find(&users, 1).Error
gorm 最佳实践
- 数据序列化与SQL表达式
- 批量数据操作
// 批量创建
db.Create(&users)
db.CreateInBatches(users, 100)
// 批量查询
rows, err := db.Model(&User{}).Where("role = ?", "admin").Rows()
for rows.Next() {
// 方法1: sql.Rows Scan
row.Scan(&name, &age, &email)
// 方法2: gorm ScanRows
db.ScanRows(rows, &user)
}
DB.Where("role = ?", "admin").FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {})
- 代码复用, 分库分表, Sharding 以从http请求中提取分页信息为例
func Paginate(r *http.Request) func(db *gorm.DB) *gorm.DB {
return func (db *gorm.DB) *gorm.DB {
page, _ := strconv.Atoi(r.Query("page"))
if page == 0 {
page = 1
}
pageSize, _ := strconv.Atoi(r.Query("page_size"))
pageSize = Math.min(100, Math.max(10, pageSize))
offset := (page - 1) * pageSize
return db.Offset(offset).Limit(pageSize)
}
}
db.Scopes(Paginate(r)).Find(&users)
- 混沌系统/压测 针对安全性要求较高的系统, 故意产生错误数据, 让系统识别
- Logger/Trace 支持在全局/会话级别设置logger
type Interface interface {
LogMode(LogLevel) Interface
Info(context.Context, string, ...interface{})
Warn(context.Context, string, ...interface{})
Error(context.Context, string, ...interface{})
Trace(context.Context, begin time.Time fc func() (sql string, rowsAffected int64), err error)
}
- Migrator
// 自动迁移
db.AutoMigrate(&User{})
// 指定迁移方式
m := gormigrate.New(db, gormigrate.DefaultOptions, []*gormigrate.Migration{
{
ID: "201608301400",
Migrate: func(tx *gorm.DB) error {
type User struct {
Name string
}
return tx.Model(&User{}).AddColumn(&User{})
},
Rollback: func(tx *gorm.DB) error {
return tx.Model(&User{}).DropColumn(&User{})
}
}
})
m.Migrate()
- Gen 代码生成/Raw SQL
- 安全问题