存储与数据库简介 | 青训营笔记

193 阅读19分钟

存储与数据库简介 | 青训营笔记

存储系统

概念:一个提供了读写、控制类接口,能够安全有效地把数据持久化的软件,就可以称为存储系统。

特点:

  • 作为后端软件的底座,性能敏感
  • 存储系统软件架构,容易受硬件影响
  • 存储系统代码,即 "简单" 又 "复杂"

RAID

背景:

  • 单块大容量磁盘价格 > 多块小容量磁盘
  • 单块磁盘写入性能 <多块磁盘的并发写入性能
  • 单块磁盘的容错能力有限,不够安全、

RAID 0

  • 多块磁盘简单组合
  • 数据条带化存储,提高磁盘带宽
  • 没有额外的容错设计

RAID 1

  • 一块磁盘对应一块额外镜像盘
  • 真实空间利用率仅50%
  • 容错能力强

RAID 0 + 1

  • 结合了RAID 0 和 RAID 1
  • 真实空间利用率仅50%
  • 容错能力强,写入带宽好

数据库

关系是什么?

关系,即集合,是任意元素组成的若干有序偶对,反应了事物间的关系

关系代数,即对关系作运算的抽象查询语言:交、并、笛卡尔积

SQL是什么?

SQL(Structured Query Language,结构化查询语言)是用来操作关系数据库的语言。

  1. SQL语句及其种类: SQL 用关键字、表名、列名等组合而成的一条语句(SQL 语句)来描述操作的内容。关键字是指那些含义或使用方法已事先定义好的英语单词,存在包含“对表进行查询”或者“参考这个表”等各种意义的关键字。根据对 RDBMS 赋予的指令种类的不同, SQL 语句可以分为以下三类。PS:实际使用的 SQL 语句当中有 90% 属于 DML。

    1. DDL(Data Definition Language,数据定义语言) 用来创建或者删除存储数据用的数据库以及数据库中的表等对象。 DDL 包含以下几种指令。
      1. CREATE: 创建数据库和表等对象
      2. DROP: 删除数据库和表等对象
      3. ALTER: 修改数据库和表等对象的结构
    2. DML(Data Manipulation Language,数据操纵语言) 用来查询或者变更表中的记录。 DML 包含以下几种指令。
      1. CREATE: 创建数据库和表等对象
      2. DROP: 删除数据库和表等对象
      3. ALTER: 修改数据库和表等对象的结构
    3. DCL(Data Control Language,数据控制语言) 用来确认或者取消对数据库中的数据进行的变更。除此之外,还可以对 RDBMS 的用户是否有权限操作数据库中的对象(数据库表等)进行设定。 DCL 包含以下几种指令。
      1. COMMIT: 确认对数据库中的数据进行的变更
      2. ROLLBACK: 取消对数据库中的数据进行的变更
      3. GRANT: 赋予用户操作权限
      4. REVOKE: 取消用户的操作权限
  2. SQL的基本书写规则:

    1. SQL语句要以分号(;)结尾;
    2. SQL语句不区分关键字大小写,插入表中的数据区分大小写;
    3. 常数的书写方式是固定的,字符串和日期常数需要使用单引号(')括起来,数字常数无需加注单引号(直接书写数字即可);
    4. 单词间需要用半角空格或者换行来分隔,不能使用全角空格作为单词的分隔符;
  3. 表的创建

    1. 数据库的创建(CREEATE DATABASE语句):CREATE DATABASE <数据库名称>;

    2. 表的创建(CREATE TABLE语句):

    3. 命名规则:只能使用半角英文字母、数字、下划线(_)作为数据库、表和列的名称 ,且名称必须以半角英文字母开头。同一个表中不能创建两个名称相同的列。

      1. 数据类型的指定:所有的列都必须指定数据类型,数据类型表示数据的种类,包括数字型、 字符型和日期型等。每一列都不能存储与该列数据类型不符的数据。下面介绍四种基本的数据类型:
        1. INTEGER型:用来指定存储整数的列的数据类型(数字型),不能存储小数。
        2. CHAR型:CHAR 是 CHARACTER(字符)的缩写,是用来指定存储字符串的列的数据类型(字符型),可在括号中指定字符串的长度,以定长字符串的形式存储,即字符串达不到指定的最大长度时,使用半角空格补充。
        3. VARCHAR型:同CHAR类型一样,但其为可变长字符串,即字符串达不到指定的最大长度,不会以空格补充。
        4. DATE型:用来指定存储日期(年月日)的列的数据类型(日期型)。
    4. 约束的设置:约束是除了数据类型之外,对列中存储的数据进行限制或者追加条件的功能,例如:约定单元格输入不能为空NOT NULL,约定主键等。

  4. 表的删除和更新

    1. 表的删除(DROP TABLE语句):DROP TABLE <表名>;,需要注意的是,删除的表是无法恢复的。

    2. 表定义的更新(ALTER TABLE 语句):

      1. 增加列使用的语法:ALTER TABLE <表名> ADD COLUMN <列的定义>;
      2. 删除列使用的语法:ALTER TALBE <表名> DROP COLUMN <列名>;,删除的数据无法恢复。
    3. 向表中插入数据:在MySQL中运行时,需要将①中的BEGIN TRANSACTION;改写为START TRANSACTION。

    4. 表名的变更:RENAME TABLE <变更前的名称> to <变更后的名称>

什么是数据库?

  1. 数据库(Database, DB)是将大量数据保存起来,通过计算机加工而成的可以进行高效访问的数据集合
  2. 用来管理数据库的计算机系统称为数据库管理系统(Database Management System,DBMS)。
  3. 为什么DBMS那么重要?因为文本文件或Excel的局限性:
    1. 无法多人共享数据
    2. 无法提供操作大量数据所需的格式
    3. 实现读写自动化需要编程能力
    4. 无法应对突发事故
  4. DBMS的种类
    1. 层次数据库(Hierarchical Database, HDB):最古老的数据库之一,它把数据通过层次结构(树形结构)的方式表现出来。
    2. 关系数据库(Relational Database, RDB):关系数据库是现在应用最广泛的数据库。
    3. 面向对象数据库(Object Oriented Database, OODB):把数据以及对数据的操作集合起来以对象为单位进行管理。
    4. XML数据库(XML Database, XMLDB):XML 数据库可以对 XML 形式的大量数据进行高速处理。
    5. 键值存储系统(Key-Value Store, KVS):这是一种单纯用来保存查询所使用的主键(Key)和值(Value)的组合的数据库。

关系型数据库

特点:

  • 结构化友好

  • 支持事务(ACID)

    A(tomicity),事务内的操作要么全做,要么不做

    C(onsistency),事务执行前后,数据状态是一致的

    I(solation),可以隔离多个并发事务,避免影响

    D(urability),事务一旦提交成功,数据保证持久性

  • 支持复杂查询语言

主流产品剖析

单机存储

概念:单个计算机节点上的存储软件系统,一般不涉及网络交互

分为:本地文件系统、key-value存储

本地文件系统

文件系统的管理单元:文件

文件系统接口:文件系统繁多,如 Ext2/3/4、sysfs、rootfs等,但都遵循VFS的统一抽象接口

Linux文件系统的两大数据结构:Index Node & Directory Entry

Index Node

记录文件元数据,如id、大小、权限、磁盘位置等。inode是一个文件的唯一标识,会被存储到磁盘上

Directory Entry

记录文件名、inode指针、层级关系(parent等)。dentry是内存结构,与inode的关系是N:1(hardlink的实现)

键值存储系统

常见使用方式:put(k,v)、get(k)

常见数据结构:LSM数,某种程度系统读性能,追求写性能

拳头产品:RocksDB

转存失败,建议直接上传图片文件

分布式存储

概念:在单机存储基础上实现了分布式协议,涉及大量网络交互

HDFS(大数据时代的基石)

核心特点:

  • 支持海量数据存储
  • 高容错性
  • 弱POSIX语义
  • 使用普通x86服务器,性价比高

Ceph(开源分布式存储系统的万金油)

核心特点:

  • 一套系统支持对象接口、块接口、文件接口,但是一切皆对象
  • 数据写入采用主备复制模型
  • 数据分布模型采用 CRUSH算法:HASH + 权重 + 随机抽签

单机数据库

关系型数据库

产品:

商业:Oracle

开源:MySQL、PostgreSQL

关系型数据库通用组件:

MySQL:

Query Engine——负责解析query,生成查询计划

Txn Manager——负责事务并发管理

Lock Manager——负责锁相关的策略

Storage Engine——负责组织内存/磁盘数据结构

Replication——负责主备同步

PostgreSQL

关键内存数据结构:B-tree、B+-Tree、LRU List 等

关键磁盘数据结构:WriteAheadLog(RedoLog)、Page

非关系型数据库

产品:

MongoDB

Redis

Elasticsearch

MongoDB

面向 [文档] 存储

文档可序列化成 JSON/BSON,支持嵌套

存在 [collection],collection=文档的集合

存储和构建搜索能力依赖wiredTiger引擎

4.0后开始支持事务(多文档、跨分片多文档等)

常用client/SDK交互,可通过插件转译支持弱SQL

Redis

数据结构丰富(hash表、set、zset、list)

C语言实现,超高性能

主要基于内存,但支持AOF/RDB持久化

常用redis-cli/多语言SDK交互

Elasticsearch

面向 [文档] 存储

文档可序列化成 JSON,支持嵌套

存在 [index],index=文档的集合

存储和构建搜索能力依赖Lucene引擎

实现了大量搜索数据结构 & 算法

支持RESTFUL API,也支持弱SQL交互

分布式数据库

解决容量问题

解决弹性问题

解决性价比问题

更多要解决的: 单写 vs 多写、从磁盘弹性到内存弹性、分布式事务优化

新技术演进

SPDK

Storage Performance Development Kit

1、Kernel Space —> User Space

  • 避免 syscall 带来的性能损耗,直接从用户态访问磁盘

2、中断 —> 轮询

  • 磁盘性能提高后,中断次数随之上升,不利于IO性能
  • SPDK poller 可以绑定特点的 cpu 核不断轮询,减少cs,提高性能

3、无锁数据结构

  • 使用 Lock-free queue,降低并发时的同步开销

高性能硬件

1、RDMA网络

  • 传统的网络协议栈,需要基于多层网络协议处理数据包,存在用户态&内核态的切换,足够通用但性能不是最佳
  • RDMA是kernel bypass的流派,不经过传统的网络协议栈,可以把用户态虚拟内存映射给网卡,减少拷贝开销,减少cpu开销

2、Persistent Memory

在NVMe SSD 和 Main Memory间有一种全新的存储产品:Persistent Memory

  • IO 时延介于 SSD 和Memory之间,约百纳秒量级
  • 可以用作易失性内存(memory mode),也可以用作持久化介质(app-direct)

3、可编程交换机

P4 Switch,配有编译器、计算单元、DRAM,可以在交换机层对网络包做计算逻辑。在数据库场景下,可以实现缓存一致性协议等

4、CPU/GPU/DPU

CPU:从multi-core走向many-core

GPU:强大的算力&越来越大的显存空间

DPU:异构计算,减轻CPU的工作负载

GROM

设计原理

1、SQL是怎么生成的

2、插件是怎么工作的

3、ConnPool是什么

3、Dialector

功能特点

GORM是一款 设计简洁、功能强大、自由扩展的全功能 ORM

设计原则:API精简、测试优先、最小惊讶、灵活扩展、无依赖 可信赖

功能完善:

  • 关联:一对一、一对多、单表自关联、多态;Preload、Joins预加载、级联删除;关联模式;自定义关联表
  • 事务:事务代码块、嵌套事务、Save Point
  • 多数据库、读写分离、命名参数、Map、子查询、分组条件、代码共享、SQL表达式(查询、创建、更新)、自动选字段、查询优化器
  • 字段权限、软删除、批量数据处理、Prepared Stmt、自定义类型、命名策略、虚拟字段、自动track时间、SQL Builder、Logger
  • 代码生成、复合主键、Constraint、Prometheus、Auto Migration、跨数据库兼容。。。
  • 多模式灵活自由扩展
  • Developer Friendly

基本用法

创建


user := User{Name: "Jinzhu", Age: 18, Birthday: time.Now()}
​
result := db.Create(&user) // 通过数据的指针来创建
​
user.ID             // 返回插入数据的主键
result.Error        // 返回 error
result.RowsAffected // 返回插入记录的条数

批量插入

要有效地插入大量记录,请将切片传递给Create方法。GORM将生成一条SQL语句来插入所有数据并回填主键值,钩子方法也将被调用。当记录可以拆分为多个批次时,它将开始一个事务。


var users = []User{{Name: "jinzhu1"}, {Name: "jinzhu2"}, {Name: "jinzhu3"}}
db.Create(&users)
​
for _, user := range users {
  user.ID // 1,2,3
}

创建钩子

GORM允许为BeforeSave、BeforeCreate、AfterSave和AfterCreate实现用户定义的挂钩。创建记录时将调用这些钩子方法


func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
  u.UUID = uuid.New()
​
    if u.Role == "admin" {
        return errors.New("invalid role")
    }
    return
}

查询

检索单个对象

GORM 提供了 FirstTakeLast 方法,以便从数据库中检索单个对象。当查询数据库时它添加了 LIMIT 1 条件,且没有找到记录时,它会返回 ErrRecordNotFound 错误


// 获取第一条记录(主键升序)
db.First(&user)
// SELECT * FROM users ORDER BY id LIMIT 1;
​
// 获取一条记录,没有指定排序字段
db.Take(&user)
// SELECT * FROM users LIMIT 1;
​
// 获取最后一条记录(主键降序)
db.Last(&user)
// SELECT * FROM users ORDER BY id DESC LIMIT 1;
​
result := db.First(&user)
result.RowsAffected // 返回找到的记录数
result.Error        // returns error or nil
​
// 检查 ErrRecordNotFound 错误
errors.Is(result.Error, gorm.ErrRecordNotFound)

根据主键检索

如果主键是数字,则可以使用内联条件使用主键检索对象。使用字符串时,需要格外小心,避免SQL注入


db.First(&user, 10)
// SELECT * FROM users WHERE id = 10;
​
db.First(&user, "10")
// SELECT * FROM users WHERE id = 10;
​
db.Find(&users, []int{1,2,3})
// SELECT * FROM users WHERE id IN (1,2,3);

检索全部对象


// Get all records
result := db.Find(&users)
// SELECT * FROM users;
​
result.RowsAffected // returns found records count, equals `len(users)`
result.Error        // returns error

条件


// String 条件
db.Where("name = ? AND age >= ?", "jinzhu", "22").Find(&users)
// SELECT * FROM users WHERE name = 'jinzhu' AND age >= 22;
​
// Struct 条件
db.Where(&User{Name: "jinzhu", Age: 20}).First(&user)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20 ORDER BY id LIMIT 1;
​
// Map 条件
db.Where(map[string]interface{}{"name": "jinzhu", "age": 20}).Find(&users)
// SELECT * FROM users WHERE name = "jinzhu" AND age = 20;
​
// 选择特定字段
db.Select("name", "age").Find(&users)
// SELECT name, age FROM users;

迭代

GORM 支持通过行进行迭代


rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
defer rows.Close()
​
for rows.Next() {
  var user User
  // ScanRows 方法用于将一行记录扫描至结构体
  db.ScanRows(rows, &user)
​
  // 业务逻辑...
}

更新

保存所有字段

Save 会保存所有的字段,即使字段是零值


db.First(&user)
​
user.Name = "jinzhu 2"
user.Age = 100
db.Save(&user)
// UPDATE users SET name='jinzhu 2', age=100, birthday='2016-01-01', updated_at = '2013-11-17 21:34:10' WHERE id=111;

Save 是一个组合功能。如果保存值不包含主键,它将执行Create,否则将执行Update(使用所有字段)。


db.Save(&User{Name: "jinzhu", Age: 100})
// INSERT INTO `users` (`name`,`age`,`birthday`,`update_at`) VALUES ("jinzhu",100,"0000-00-00 00:00:00","0000-00-00 00:00:00")
​
db.Save(&User{ID: 1, Name: "jinzhu", Age: 100})
// UPDATE `users` SET `name`="jinzhu",`age`=100,`birthday`="0000-00-00 00:00:00",`update_at`="0000-00-00 00:00:00" WHERE `id` = 1

更新列

单列:

使用Update更新单列时,它需要有任一条件,否则会引发错误ErrMissingWhereClause。当使用Model方法并且其值具有主键时,主键将用于构建条件,例如:


// Update with conditions
db.Model(&User{}).Where("active = ?", true).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE active=true;// User's ID is `111`:
db.Model(&user).Update("name", "hello")
// UPDATE users SET name='hello', updated_at='2013-11-17 21:34:10' WHERE id=111;/*
更新多列:Updates支持使用struct或map[string]接口{}进行更新,当使用struct进行更新时,它只会默认更新非零字段 
*/
// Update attributes with `struct`, will only update non-zero fields
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;// Update attributes with `map`
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

多列:

更新多列:Updates支持使用struct或map[string]接口{}进行更新,当使用struct进行更新时,它只会默认更新非零字段


// Update attributes with `struct`, will only update non-zero fields
db.Model(&user).Updates(User{Name: "hello", Age: 18, Active: false})
// UPDATE users SET name='hello', age=18, updated_at = '2013-11-17 21:34:10' WHERE id = 111;
​
// Update attributes with `map`
db.Model(&user).Updates(map[string]interface{}{"name": "hello", "age": 18, "active": false})
// UPDATE users SET name='hello', age=18, active=false, updated_at='2013-11-17 21:34:10' WHERE id=111;

删除

删除一条记录

删除一条记录时,删除对象需要指定主键,否则会触发批量删除,例如:


// Email 的 ID 是 `10`
db.Delete(&email)
// DELETE from emails where id = 10;
​
// 带额外条件的删除
db.Where("name = ?", "jinzhu").Delete(&email)
// DELETE from emails where id = 10 AND name = "jinzhu";

根据主键删除

GORM 允许通过主键(可以是复合主键)和内联条件来删除对象,它可以使用数字


db.Delete(&User{}, 10)
// DELETE FROM users WHERE id = 10;
​
db.Delete(&User{}, "10")
// DELETE FROM users WHERE id = 10;
​
db.Delete(&users, []int{1,2,3})
// DELETE FROM users WHERE id IN (1,2,3);

批量删除

如果指定的值不包括主属性,那么 GORM 会执行批量删除,它将删除所有匹配的记录


db.Where("email LIKE ?", "%jinzhu%").Delete(&Email{})
// DELETE from emails where email LIKE "%jinzhu%";
​
db.Delete(&Email{}, "email LIKE ?", "%jinzhu%")
// DELETE from emails where email LIKE "%jinzhu%";

可以将一个主键切片传递给Delete 方法,以便更高效的删除数据量大的记录


var users = []User{{ID: 1}, {ID: 2}, {ID: 3}}
db.Delete(&users)
// DELETE FROM users WHERE id IN (1,2,3);
​
db.Delete(&users, "name LIKE ?", "%jinzhu%")
// DELETE FROM users WHERE name LIKE "%jinzhu%" AND id IN (1,2,3);

模型

模型定义

模型是标准的 struct,由 Go 的基本数据类型、实现了 ScannerValuer 接口的自定义类型及其指针或别名组成

例如:


type User struct {
  ID           uint
  Name         string
  Email        *string
  Age          uint8
  Birthday     *time.Time
  MemberNumber sql.NullString
  ActivatedAt  sql.NullTime
  CreatedAt    time.Time
  UpdatedAt    time.Time
}

约定

GORM 倾向于约定优于配置 默认情况下,GORM 使用 ID 作为主键,使用结构体名的 snake_cases 作为表名,字段名的 snake_case 作为列名,并使用 CreatedAtUpdatedAt 字段追踪创建、更新时间。

如果遵循 GORM 的约定,我们就可以少写的配置、代码。 如果约定不符合您的实际要求,GORM 允许你配置它们

关联

Belongs to

belongs to 会与另一个模型建立了一对一的连接。 这种模型的每一个实例都“属于”另一个模型的一个实例。

例如,您的应用包含 user 和 company,并且每个 user 能且只能被分配给一个 company。下面的类型就表示这种关系。 注意,在 User 对象中,有一个和 Company 一样的 CompanyID。 默认情况下, CompanyID 被隐含地用来在 UserCompany 之间创建一个外键关系, 因此必须包含在 User 结构体中才能填充 Company 内部结构体。


// `User` 属于 `Company`,`CompanyID` 是外键
type User struct {
  gorm.Model
  Name      string
  CompanyID int
  Company   Company
}
​
type Company struct {
  ID   int
  Name string
}

要定义一个 belongs to 关系,数据库的表中必须存在外键默认情况下,外键的名字,使用拥有者的类型名称加上表的主键的字段名字

例如,定义一个User实体属于Company实体,那么外键的名字一般使用CompanyID。

GORM同时提供自定义外键名字的方式,如下例所示。


type User struct {
  gorm.Model
  Name         string
  CompanyRefer int
  Company      Company `gorm:"foreignKey:CompanyRefer"`
  // 使用 CompanyRefer 作为外键
}
​
type Company struct {
  ID   int
  Name string
}

Has One

has one 与另一个模型建立一对一的关联,但它和一对一关系有些许不同。 这种关联表明一个模型的每个实例都包含或拥有另一个模型的一个实例。

例如,您的应用包含 user 和 credit card 模型,且每个 user 只能有一张 credit card。


// User 有一张 CreditCard,UserID 是外键
type User struct {
  gorm.Model
  CreditCard CreditCard
}
​
type CreditCard struct {
  gorm.Model
  Number string
  UserID uint
}

GORM 为 has onehas many 提供了多态关联支持,它会将拥有者实体的表名、主键值都保存到多态类型的字段中。


type Cat struct {
  ID    int
  Name  string
  Toy   Toy `gorm:"polymorphic:Owner;"`
}
​
type Dog struct {
  ID   int
  Name string
  Toy  Toy `gorm:"polymorphic:Owner;"`
}
​
type Toy struct {
  ID        int
  Name      string
  OwnerID   int
  OwnerType string
}
​
db.Create(&Dog{Name: "dog1", Toy: Toy{Name: "toy1"}})
// INSERT INTO `dogs` (`name`) VALUES ("dog1")
// INSERT INTO `toys` (`name`,`owner_id`,`owner_type`) VALUES ("toy1","1","dogs")

Has Many

has many 与另一个模型建立了一对多的连接。 不同于 has one,拥有者可以有零或多个关联模型。

例如,您的应用包含 user 和 credit card 模型,且每个 user 可以有多张 credit card。


// User 有多张 CreditCard,UserID 是外键
type User struct {
  gorm.Model
  CreditCards []CreditCard
}
​
type CreditCard struct {
  gorm.Model
  Number string
  UserID uint
}

Many To Many

Many to Many 会在两个 model 中添加一张连接表。

例如,您的应用包含了 user 和 language,且一个 user 可以说多种 language,多个 user 也可以说一种 language。


// User 拥有并属于多种 language,`user_languages` 是连接表
type User struct {
  gorm.Model
  Languages []Language `gorm:"many2many:user_languages;"`
}
​
type Language struct {
  gorm.Model
  Name string
}

关联模式

关联模式包含一些在处理关系时有用的方法


// 开始关联模式
var user User
db.Model(&user).Association("Languages")
// `user` 是源模型,它的主键不能为空
// 关系的字段名是 `Languages`
// 如果匹配了上面两个要求,会开始关联模式,否则会返回错误
db.Model(&user).Association("Languages").Error

查找关联:

查找所有匹配的关联记录


db.Model(&user).Association("Languages").Find(&languages)

查找带条件的关联


codes := []string{"zh-CN", "en-US", "ja-JP"}
db.Model(&user).Where("code IN ?", codes).Association("Languages").Find(&languages)
​
db.Model(&user).Where("code IN ?", codes).Order("code desc").Association("Languages").Find(&languages)

添加关联

many to manyhas many 添加新的关联;为 has one, belongs to 替换当前的关联


db.Model(&user).Association("Languages").Append([]Language{languageZH, languageEN})
​
db.Model(&user).Association("Languages").Append(&Language{Name: "DE"})
​
db.Model(&user).Association("CreditCard").Append(&CreditCard{Number: "411111111111"})

替换关联

用一个新的关联替换当前的关联


db.Model(&user).Association("Languages").Replace([]Language{languageZH, languageEN})
​
db.Model(&user).Association("Languages").Replace(Language{Name: "DE"}, languageEN)

删除关联

如果存在,则删除源模型与参数之间的关系,只会删除引用,不会从数据库中删除这些对象。


db.Model(&user).Association("Languages").Delete([]Language{languageZH, languageEN})
db.Model(&user).Association("Languages").Delete(languageZH, languageEN)

级联删除

你可以在删除记录时通过 Select 来删除具有 has one、has many、many2many 关系的记录,例如:


// 删除 user 时,也删除 user 的 account
db.Select("Account").Delete(&user)
​
// 删除 user 时,也删除 user 的 Orders、CreditCards 记录
db.Select("Orders", "CreditCards").Delete(&user)
​
// 删除 user 时,也删除用户所有 has one/many、many2many 记录
db.Select(clause.Associations).Delete(&user)
​
// 删除 users 时,也删除每一个 user 的 account
db.Select("Account").Delete(&users)