DATABASE/SQL 与 GORM 设计与实践 笔记 | 青训营笔记

147 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第5篇笔记

一、理解database/sql

databse/sql 基本用法,设计原理,基础概念


1.0 基本概念

databse/sql包:golang标准库提供的和数据库交互的包,其理念是对不同的数据库实现提供一套一致的接口给用户使用

DSN:Data Source Name,数据库名称,用于描述与数据连接的关联数据结构


1.1 基本用法

import (
  "database/sql"
  
  _ "github.com/go-sql-driver/mysql"
)
​
func main() {
    // 使用 driver + DSN 初始化 DB 连接
    db, err := sql.Open("mysql", "user:password@tcp(127.0.0.1:3306)/dbname")
    
    // 执行一条 SQL,通过 rows 取回 返回数据
    rows, err := db.Query("select id, name from users wherer id = ?", 1)
    if err != nil {
        // xxx
    }
    // 处理完毕,释放
    defer rows.Close()
    // jinzhu:上面这句其实有问题, err直接close,可能丢失错误信息
    // jinzhu大佬 更好的写法
    // defer func() {
    //     err = rows.Close()
    // } 
  
    var users []User
    for rows.Next() {
        var userUser
        err := rows.Scan(&user.ID, &user.Name)
        
        if err != nil {
            // ...
        }
        
        users = append(users, user)
    }
    
    // 这个错误处理也很重要
    if rows.Err() != nil {
        // ...
    }
}

连接池

for i := 0 i < maxBadConRetries; i++ {
    dc, err := db.conn(ctx, strategy)
      // 有空闲连接 -> reuse - max life time
      // 新建连接 -> max open...
    
    defer dc.db.putConn(dc, err, true)
      // validateConnction 有无错误
      // max life time, max idle conn 检查
    
    // 连接实现 driver。Queryer, driver.Execer 等 interface
    if err == nil {
        err = dc.ci.Qiery(sql, args...)
    }
    
    isBadConn = errors.Is(err, driver.ErrBadConn)
    if !isBadConn {
        break
    }
}

jinzhu 大佬:

  • maxBadConnRetries:不可配置,如果能配置就更好了
  • 有可能sql已经被执行,但是仍然产生错误,出发重试,sql执行多次

1.2 设计原理

对上层应用提供操作接口,对下层数据库提供连接接口和操作接口,让实现方来实现

值得一提的是,使用了池化技术,对比较贵重的资源(数据库连接)

截屏2022-05-13 23.48.25.png


二、GORM使用简介

GORM 基本用法,Model定义,惯例约定,关联操作

看文档就完事儿了


三、GORM设计原理

SQL生成,插件扩展,ConnPool、Dialector

  • SQL 是怎么生成的
  • 插件是怎么工作的
  • ConnPool是什么
  • Dialector

3.0 GORM的位置

截屏2022-05-14 01.05.33.png


3.1 SQL是怎样生成的

截屏2022-05-14 01.37.27.png

从上面的图我们可以看到,一个SQL语句是由很多的子句构成的,以下是使用gorm生成SQL执行的代码和子句的对应关系

截屏2022-05-14 01.40.51.png

其中,chain method会对整个gorm statement起到筛选的作用,而 finisher method会决定最终的类型以及执行的细节。jinzhu 大佬:这么设计是为了三件事:

  • 自定义子句 截屏2022-05-14 01.49.19.png
  • 拓展子句 截屏2022-05-14 01.46.09.png
  • 选择子句:可以在不同的数据库对应的sql中使用对应的sql

3.2 插件如何工作

截屏2022-05-14 01.53.39.png


3.3 ConnPool

截屏2022-05-14 02.10.02.png

有了ConnPool这个接口口,我们可以做读写分离,自定义编排存储流程的插件,自定义事务配置,对接第三方api代替sql进行存储等等操作,总的来说就是通过一个中间层干预了存储层和代码层的对接

3.4 Dialector

实际使用数据库的时候,一些参数的配置往往能四两拨千斤,对性能优化起到不小的作用,但是查阅各种文档学习这些细枝末节未免太过头重脚轻,Dialector是gorm提供的一个接口,用户可以通过实现该接口,在使用dsn连接数据源的时候加入一些配置项,从而达到减少配置的效果


四、GORM最佳实践

GORM最佳实践、企业级开发、FAQ

  1. 数据序列化 与 SQL表达式
  2. 批量数据操作
  3. 代码复用,分库分表,Sharding
  4. 混沌工程,压测
  5. Logger,Trace
  6. Migrator
  7. Gen 代码生成 / Raw SQL
  8. 安全