这是我参与「第三届青训营-后端场」笔记创作活动的第5篇笔记。
1. 理解database/sql
1.1 基本用法-Quick Start
import(
"database/sql"
_ "github.com/go-sql-driver/mysql" //使用mysql的driver
)
func main(){
db, err := sql.Open("mysql","user:password@tcp(127.0.0.1:3306)/databased")
rows, err := db.Query("sql查询语句,无需分号结尾",1)
if err != nil{
}
defer func(){ //此时可能会有错误发生,此处与只执行defer rows.Close()相比防止了err丢失
err = rows.Close() //释放链接防止资源泄漏。
}
var users []User //假设rows读取了用户信息
for rows.Next(){
var user User
err := rows.Scan(&user.ID, &user.Name) //传参
if err != nil {
}
users = append(users,user)
}
if rows.Err() != nil {
}
}
1.2 设计原理
database/sql对应用程序(上层应用)提供标准api操作接口,对数据库提供简单的驱动接口(连接接口与操作接口)。在database/sql包内部实现连接池的管理。
实现不同的数据库驱动就是实现一个额外链接、操作接口。而暴露给应用程序的接口不变,以此实现应用程序使用不同的数据库driver。
连接池使用了池化技术,当遇到请求量大的情况,池化技术就可以提升效率。把昂贵、费时的资源放入特定的池子中,减少创建、销毁、垃圾回收的损耗。
连接池:
连接池配置:
func(db *DB) SetConnMaxIdleTime(d time.Duration)
func(db *DB) SetConnMaxLifetime(d time.Duration)
func(db *DB) SetMaxIdleConns(n int)
func(db *DB) SetMaxOpenConns(n int)
连接池状态:
func(db *DB) Stats() DBStats
伪实现:
for i := 0; i < maxBadConnRetries; i++ { // 最大重试次数
//从连接池获取链接或通过driver新建连接
dc, err := db.conn(ctx, strategy)
//有空闲连接 -> reuse -> max life time
//新建连接 -> max open...
//将连接放回连接池
defer dc.db.putConn(dc, err, true)
//validateConnection 有无错误
//max life time, max idle conns 检查
//连接实现 driver, Querter,driver.Execer等接口
if err == nil {
err = dc.ci.Query(sql,args...)
}
isBadConn = errors.Is(err, driver.ErrBadConn)
if !isBadConn{
break
}
}
连接接口:
//Driver 接口
type Driver interface {
//Open returns a new connection to the database.
Open(name string) (Conn, error)
}
//注册全局driver
func Register(name string,driver driver.Driver){
driversMu.Lock()
if driver == nil {
panic("sql:Register driver is nil")
}
if _, dup := dirvers[name]; dpu {
panic("sql:Register called twice for driver"+name)
}
drivers[name] = driver
}
缺点:driver的名字可能长而难记,不同人的driver名字不能冲突,后来者使用名字会被提示名字被占用。
新版本的go支持使用结构体:
connector, err := mysql.NewConnector(&mysql.Config{User:"gorm", Passwd:"gorm", Net:"tcp", Addr:"127.0.0.1:3306", DBName:"gorm", ParseTime:true,}
2. GORM基础使用
中文文档 gorm.cn/zh_CN/docs/
GORM支持关联,并在此基础上进行预加载和级联删除。
3. GORM原理
应用程序使用GORM,GORM通过操作接口使用database/sql。database/sql再通过连接接口和操作接口使用数据库。
通过一行代码提升服务器性能:interpolateParams=true将预编译的SQL缓存(false时也预编译,但不缓存,造成浪费,默认为false是因为能防止多编码环境下的数据注入)。
使用字节的默认配置:
import(
"code.byted.org/gorm/bytedgorm"
"gorm.io/gorm"
)
//初始化
DB, err := gorm.Open(
//psm的格式为p.s.m 无需 _write, _read等后缀, dbname为数据库名
bytedgorm.MySQL("p.s.m"/*数据库PSM*/,"dbname"/*数据库名*/).WithReadReplicas(),
bytedgorm.WithDefaults(),
)
3.1 SQL是怎么生成的
每一条GORM语句都能生成一个GORM STATEMENT对象(对应一个SQL语句),而每个GORM STATEMENT由多个Chain Method(为最后生成的SQL代码添加子句)和一个Finisher Method(标志语句结束,决定返回类型以及开始执行)构成。
假设Finisher Method为Find函数,其内部会查询是否存在[SELECT,FROM,WHERE,GROUP BY,ORDER BY,LIMIT]操作,其中前两个操作必须存在,后面的操作若不存在则设为默认值。然后按照操作生成SQL语句,交给连接池。
优点:灵活的生成SQL子句,方便扩展子句,自定义子句(可支持不同数据库、不同版本SQL语言)
3.2 插件是怎么工作的
Finisher Method ----> (决定Statement类型 ----> 执行Callbacks ----> 生成SQL并执行)
括号中为插件系统,即是可扩展、可替换的。
Callbacks共有6种:Create,Query,Update,Delete,Row,Raw。每种类型中都有多个Callback方法。我们可以通过一些语句对Callback进行注册、删除、替换、查询、指定顺序等操作。
多租户问题:通过租户id过滤信息,每个租户只能得到自己相关的信息。
多数据库、读写分离:通过主从数据库,主数据库可写,从数据库用于读。
3.3 连接池是什么
GORM将SQL语句放入连接池,连接池可以为多个数据库提供连接。
3.4 Dialector是什么
driver不同数据库可以使用不同数据库的方言。
Dialector可以定制SQL生成、定制GORM插件、定制连接池、定制企业特性逻辑。
4. GORM实用功能
- 数据序列化与SQL表达式
- 批量数据操作
- 代码复用、分库分表、Sharding
- 混沌工程、压力测试
- Logger/Trace
- Migrator
- Gen代码生成/Raw SQL
- 安全