这是我参与「第三届青训营 -后端场」笔记创作活动的的第8篇笔记, 本次课主要讲了database/sql和 Gorm的用法.
一、database/sql
基本用法
注册Driver
一点小问题:
- 上面连接中,连接信息是很长的字符串,有的时候字符串转义等会出现问题,为了解决这个问题,后来Go语言官方提供了一个新的方式连接
- 新的方式使用结构体的方式连接
设计原理
DB连接的类型
- 直接连接 / Conn
- 预编译 / Stmt
- 事务
处理返回数据的几种形式
- Exec/ExecContext -> Result: 执行sql,可以获取是否成功,lastInsertId等信息
- Query / QueryContext -> Rows (Columns): 将请求结果返回
- QueryRow / QueryRowContext -> Row (Rows 简化)
具体实现
二、Gorm基础使用
基本用法
模型定义
软件设计范式:约定优于配置
- 表名为struct name的snake_cases复数格式
- 字段名为field name 的snake_case单数格式
- ID/Id字段为主键,如果为数字,则为自增主键
- CreatedAt 字段,创建时,保存当前时间
- UpdatedAt字段,创建、更新时,保存当前时间
- gorm.DeletedAt 字段,默认开启 soft delete模式
关联介绍
- has one
- has many
- 多态 has many
- ...
关联操作
Preload/Joins预加载
- 一条sql不一定比三条sql性能好
- 所以要测试preload和join的性能比较
级联删除
保障数据没有孤儿数据
- 使用数据库约束
- 使用 select 实现级联删除,不依赖数据库约数和软删除功能
三、Gorm设计原理
Gorm是在应用程序和database/sql之间的一层,操作sql的接口
SQL是怎么生成的
- “找出35岁以上不是管理者的打工人”
- Chain Method:可以添加字句的
- Finisher Method: 决定类型 & 执行的方法
Chain Method实现
Finish Method 实现
为什么这么设计
- 自定义Clause Builder
- 方便扩展Clause
- 自由选择 Clauses
不同数据库甚至不同版本支持的sql不同
- 根据不同版本来实现
扩展子句
选择子句
\
插件是怎么工作的
Create的callback设计
修改callback
为什么要这么设计
完成灵活定制,自由扩展
- 怎么支持多租户
- 支持多数据库,读写分离
- 加解密,混沌工程...
多租户
查询的时候往往加上id等过滤条件,希望查询的时候自动加上这些条件
多数据库,读写分离
ConnPool是什么
- 重新定义了一个接口interface ConnPool 在Gorm和数据库之间
PrepareStmt模式:
-
全局模式: 缓存不含参数部分,尽量不要拼接sql,一定要作为参数执行
- 这里缓存会有容量限制,达到容量就无法缓存了
- 会话模式:后续的会话操作都会预编译并缓存
- 查找缓存的预编译 SQL
- 未找到,将收到的SQL和Vars预编译
- 使用缓存的预编译SQL执行
通过ConnPool接口更改下游数据库
利用ConnPool 开发缓存插件
通过一行配置提升性能
- interpolateParams=false
- 这里预编译用完就关掉了,导致利用率低
- 这个为了解决多编码环境下的注入问题,一般情况下不会有这个情况
- 可以试着关闭这个参数
字节内部封装了一些默认配置 bytedgorm
Dialector是什么
- 不同语言的实现
功能
四、Gorm最佳实践
数据序列化与 SQL表达式
SQL表达式更新创建
- gorm.Expr 使用sql表达式
- 使用GormValue
- 使用子查询SubQuery
查询
- gorm.Expr
- GormValue
- 自定义查询SQL实现接口
- SubQuery
数据序列化
- 可以对密码加解密等
批量数据操作
创建
- CreateInBatches(users, 100): 每一百条数据创建一次
查询
- 避免把数据都加载到内存,避免oom
- FindInBatches: 每100条查询数据,分批处理
更新
批量数据加速操作
- 关闭默认事务
- 默认批量导入会调用hooks方法,使用Skiphooks跳过
- 使用Prepares Statement预编译sql
- 混合使用, CreateBatchSize:默认使用批量插入,1000个一次
代码复用、分库分表、Sharding
代码复用
- 把分页逻辑抽象出来,利用Scopes方法复用抽象的逻辑
分库分表
- 根据数据选择分库分表,然后后面复用这个逻辑
Sharding
- 创建订单时,userId是2,就会把数据插入到orders_2的表里
混沌工程/压测
插入数据时篡改一些数据,检查系统状态能不能发现
Logger/Trace
- 全局模式
- 会话模式
Migrator 数据库迁移管理
- 自动迁移数据库
- 版本管理数据库
- 数据库迁移接口
- 获取字段信息的接口
Raw SQL
- Raw sql 不容易管理,改的时候容易忘
代码生成 Raw SQL - Gen
安全问题
- 要以参数的形式输入,如果直接拼接会发生sql注入的事故
Q&A
-
Gorm支持动态sql吗
- 可以使用Scopes等实现
-
外键实现的场景
- Gorm的外键和数据库的外键不一样,根据这个字段作为一个referenceId 辅助查询
-
修改数据库结构,可以更改Struct,然后AutoMigrate
-
取消点赞
- 一般倾向于保留,添加一个状态实现,并且添加更改时间
-
save和create方法区别
- save使用前会判断主键是不是存在,如果存在,就调用update,不存在,就调用create
-
gorm v1过渡gorm v2
- 不能平滑过渡
- 在做不同代码实现的时候,要考虑用户迁移成本
-
业务代码一般是通过model(和表对应的结构体)查询出来值,再赋值到返回给前端的响应体结构(vo)中吗
- gorm有 smart select