这是我参与「第三届青训营-后端场」笔记创作活动的的第5篇笔记。
基础知识
数据库
- 数据库(Database, DB)是将大量数据保存起来,通过计算机加工而成的可以进行高效访问的数据集合。
- 用来管理数据库的计算机系统称为数据库管理系统(Database Management System,DBMS)。
- DBMS的种类
- 层次数据库(Hierarchical Database, HDB):最古老的数据库之一,它把数据通过层次结构(树形结构)的方式表现出来。
- 关系数据库(Relational Database, RDB):关系数据库是现在应用最广泛的数据库。
- 面向对象数据库(Object Oriented Database, OODB):把数据以及对数据的操作集合起来以对象为单位进行管理。
- XML数据库(XML Database, XMLDB):XML 数据库可以对 XML 形式的大量数据进行高速处理。
- 键值存储系统(Key-Value Store, KVS):这是一种单纯用来保存查询所使用的主键(Key)和值(Value)的组合的数据库。
DSN(data source name)
- 一个使用相关数据结构来描述与数据源连接的字符串。
- DSN 中可能包含但不限于:
- 数据源的名字
- 数据源的位置
- 可以访问数据源的数据库驱动的名字
- 用于访问数据的用户ID
- 用于访问数据的用户密码
ORM
ORM(Object-relational mapping),中文翻译为对象关系映射,是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系数据库中。
理解 database/sql 包
基本用法
database/sql只实现了统一的接口,需要不同的数据库实现具体的 driver。- Go-MySQL-Driver 是 database/sql 包的一个 MySQL-Driver。
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func main() {
db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/hello")
rows, err := db.Query("select id, name from users where id = ?", 1)
if err != nil {
// xxx
}
defer rows.Close()
var users []User
for rows.Next() {
var user User
err := rows.Scan(&user.ID, &user.Name)
if err != nil {
// ...
}
users = append(users, user)
}
if row.Err() != nil {
// ...
}
}
设计原理
[应用程序]--(操作接口)--[database/sql]--(连接接口/操作接口)--[数据库]
- 使用连接池(池化技术)
- 连接接口1(注册 driver):
- 提供 Driver 接口(
database/sql/driver/driver.go)
type Driver interface { Open(name string) (Conn, error) }- 提供 Register 函数(
database/sql/sql.go)
func Register(name string, driver driver.Driver) { driversMu.Lock() defer driversMu.Unlock() if driver == nil { panic("sql: Register driver is nil") } if _, dup := drivers[name]; dup { panic("sql: Register called twice for driver " + name) } drivers[name] = driver }- 注册 driver (
github.com/go-sql-driver/mysql/driver.go)
func init() { sql.Register("mysql", &MySQLDriver{}) }- 业务代码
import _ "github.com/go-sql-driver/mysql" func main() { db, err := sql.Open("mysql", "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf&&parseTime=True&loc=local") } - 提供 Driver 接口(
- 连接接口2
- 提供 Connector 接口 (
database/sql/driver/driver.go)
type Connector interface { Connect(context.Context) (Conn, error) Driver() Driver }- 提供 OpenDB 函数(
database/sql/sql.go)
func OpenDB(c driver.Connector) *DB { // ... }- 业务代码
import "github.com/go-sql-driver/mysql" func main() { connector, err := mysql.NewConnector(&mysql.Config{ User: "gorm", Passwd: "gorm", Net: "tcp", Addr: "127.0.0.1:3306", DBName: "gorm", ParseTime: true, }) } db := sql.OpenDB(connector) - 提供 Connector 接口 (
GORM 的使用简介
GORM 的基本用法
import (
"gorm.io/gorm"
"gorm.io/driver/mysql"
)
func main() {
db, err := gorm.Open(
mysql.Open("user:password@tcp(127.0.0.1:3306)/hello")
)
var users []User
err = db.Select("id", "name").Find(&users, 1).Error
}
- CRUD
user := User{Name: "jinzhu", Age: 18, Birthday: time.Now()}
result := db.Create(&user)
var product Product
db.First(&product, 1) // 查询 id 为 1
db.First(&product, "code = ?", "L1212") // 查询 code 为 L1212
// 更新某个字段
db.Model(&product).Update("Price", 2000)
db.Delete(&product)
Model 定义
type Model struct {
ID uint `gorm:"primarykey"`
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt DeletedAt `gorm:"index"`
}
惯例约定
- 结构体名的蛇形命名法(小写字母+下划线)复数形式:表名
- 结构体字段名的蛇形命名法单数形式:字段名
- ID/Id 字段:主键,如果为数字则为自增主键
- CreatedAt 字段:创建的时间
- UpdatedAt 字段:创建、更新时的时间
- gorm.DeletedAt 字段:默认开启 soft delete 模式
关联介绍
- Has One, Has Many, Belongs To, Many To Many, Polymorphism, Single-table, inheritance
- 关联操作:
- CRUD
- Preload / Joins 预加载
- 级联删除
GORM 的设计原理
[应用程序]--[GORM]--(操作接口)--[database/sql]--(连接接口/操作接口)--[数据库]
SQL 生成的机制
GORM statement 是模仿 SQL statement 的组成来实现的,;例如:
db.Where("role <> ?", "manager").Where("age > ?", 35).Limit(100).Order("age desc").Find(&user)
由一系列 Chain Method 和最后的 Finisher Method 组成,其中 Chain Method 决定了 SQL Clauses,而 Finisher Method 决定了语句的类型并给予执行:
SELECT * FROM users WHERE role <> "manager" AND age > 35 ORDER BY age desc LIMIT 100
- 可以自定义 Builder。不同数据库/不同版本支持的 SQL 不同,通过自定义 Builder 隐藏具体细节。
- 可以扩展子句。例如
db.Clauses(hints.UseIndex("idx_user_name")).Find(&User{}) - 可以选择子句。处理不同数据库的 SQL 差异,例如
/* PostgreSQL*/ DELETE FROM users WHERE condition /* MySQL*/ DELETE FROM users WHERE condition ORDER BY age desc LIMIT 100
插件扩展机制
Finisher Method --> 决定 statement 类型 --> 执行 callbacks --> 生成 SQL 并执行
(_______________________插件系统_______________________)
- callback的模式:Create, Query, Update, Delete, Row, Raw
- 每一种模式对应一组 callbacks,在执行的时候一一执行所有注册的 callbacks.例如Create:
- 注册的callbacks
db.Callback().Create().Register("gorm:begin_transaction", BeginTransacntion) db.Callback().Create().Register("gorm:before_create", BeforeCreate) //...- 具体执行
db.Create()的过程(伪代码):
func Create(data interface{}) error { for _, f := range db.callbacks.creates { f() } } - 扩展插件的API
db.Callback().Create().Register()
db.Callback().Create().Remove()
db.Callback().Create().Replace()
db.Callback().Create().Get()
//...
- 因此可以实现:多租户,多数据库、读写分离,加解密等
ConnPool 扩展机制
(实现接口) (连接池)
[GORM] --> [ConnPool] --> [数据库]
↑(实现接口)
[DB Conn(database/sql)]
Dialector 扩展机制
例如:
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
中的 mysql.Open(dsn) 就是一个 dialector。通过 dialector 实现定制化的功能。
GORM 最佳实践
- 数据序列化与SQL表达式
- 批量数据操作
- 代码复用、分库分表、sharding
- 混沌工程/压测
- Logger/Trace
- Migrator
- Gen代码生成/Raw SQL
- 安全