go 和数据库

91 阅读8分钟

go和数据库

经典案例

数据产生

如一个注册新帐号的操作

数据流动

一条用户注册数据 -> 后端服务器 -> 数据库系统 -> 其他非存储系统

数据持久化

数据到达数据库系统

  1. 检验数据合法性
  2. 修改内存,用高效数据结构组织数据
  3. 写入存储介质

潜在问题

  1. 数据丢失
  2. 多人并发修改数据
  3. 为什么用数据库
  4. 只能处理结构化数据吗
  5. 有什么操作数据库的方式?要用什么编程语言

存储 & 数据库系统

存储系统:一个提供读写,控制类接口,能安全有效把数据持久化的软件,即存储系统

用户,硬盘,内存,网络

存储系统特点

  1. 作为后端软件底座,性能敏感,性能必须优化到极致
  2. 容易受硬件影响,要考虑到硬件变革
  3. 代码在IO路径上要简单,在错误处理,日志,监控,配置等方面要复杂

存储器层级结构

Persistent Memory, 近年来出现的新技术,介于内存和SSD之间,速度比SSD快,容量比内存大

数据从应用到存储介质

缓存很重要,贯穿整个存储体系

拷贝很昂贵,尽量减少

硬件设备五花八门,需要有抽象统一的接入层

RAID 技术

单机存储系统如何高性能,高可靠,高性价比

RAID:Redundant Array of Inexpensive Disks

RAID 背景

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

RAID 0

多块磁盘简单组合,数据条带化,提升磁盘带宽,没有容错能力

RAID 1

一块磁盘对应一块镜像磁盘,真实空间利用率50%,有容错能力

RAID 0+1

结合 RAID 0 和 RAID 1,数据条带化,镜像备份,有容错能力

数据库

关系模型

关系 = 集合 = 任意元素的若干有序偶对,反应现实世界的实体和实体之间的联系

关系代数 = 对关系做运算的抽象查询语言

SQL = 结构化查询语言,关系代数的具体实现

关系型数据库

关系型数据库是存储系统,但除此之外还有其他能力

  • 结构化数据友好
  • 支持事务处理
  • 支持SQL

非关系型数据库

非关系型不要求严格结构化

  • 半结构化数据友好
  • 可能支持事务处理
  • 可能支持SQL

数据库 vs 存储系统

数据库:写入关系型数据库,以表的形式存储,读取时需要解析,转换成内存中的数据结构

存储系统:写入文件,自行定义,管理结构

事务能力

ACID:原子性,一致性,隔离性,持久性

  • A:原子性,事务中的所有操作要么全部成功,要么全部失败
  • C:一致性,事务执行前后,数据库都必须处于一致状态
  • I:隔离性,事务执行过程中,对外部数据的修改,对其他事务是不可见的
  • D:持久性,事务执行成功后,对数据的修改是永久的

主流产品剖析

单机存储

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

文件系统

Linux 经典哲学:一切皆文件

文件系统管理单元:文件

Linux 文件系统两大数据结构,inode 和 dentry

inode:索引节点,记录文件的元数据,如文件大小,创建时间,修改时间,访问时间,权限,文件指针,会被存储到磁盘上,总数在格式化文件系统时就固定了 dentry:目录项,记录文件名和inode的对应关系,是内存结构,和inode的关系是多对一(hardlink)

key-value 存储系统

key-value 存储系统:把数据按照 key-value 的形式存储,key 和 value 都是二进制数据,value 可以是结构化数据,也可以是非结构化数据

常见使用方式:put(key, value), get(key), delete(key)

常见数据结构:LSM 树,牺牲读取性能,换取写入性能

拳头产品:RocksDB

分布式存储

分布式存储系统:多个计算机节点上的存储软件系统,涉及网络交互

分布式文件系统

HDFS:Hadoop Distributed File System

大数据时代的基石

专用高级硬件很贵,数据存量也很大,要求超高吞吐

核心特点:

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

Ceph

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

核心特点:

  • 一切皆对象,支持对象接口,块接口,文件接口
  • 数据写入采用主从复制
  • 数据分布使用 CRUSH 算法

单机数据库

单机数据库:单个计算机节点上的数据库软件系统,不涉及网络交互

关系型数据库概览

商业产品:Oracle,DB2,SQL Server

开源产品:MySQL,PostgreSQL

关系型数据库通用组件:

  • Query Engine:SQL解析,优化,执行
  • Txn Manager:事务并发控制
  • Storage Engine:数据存储和索引
  • Lock Manager:锁管理
  • Replication:主从复制

关键内存数据结构:

  • B-Tree:索引,支持范围查询
  • B+Tree:索引,支持范围查询,支持顺序访问
  • LRU:缓存,支持淘汰

磁盘数据结构:

  • WriteAheadLog:预写式日志,支持事务
  • Page:数据页,支持随机访问

非关系型数据库概览

MongoDB, Redis, Elasticsearch 三足鼎立

  • 交互方式各不相同
  • schema相对灵活
  • 尝试去支持SQL

Elaticsearch:全文搜索引擎,基于Lucene,支持分布式,支持SQL,面向文档存储,可序列化为JSON, 实现大量搜索数据结构和算法

mongoDB:面向文档存储,可序列化为JSON,支持SQL,存在 collection, 为文档的集合,支持食物,由SDK支持交互可通过插件支持 SQL

Redis:面向键值存储,支持多种数据结构,主要基于内存,但也可以进行持久化,常用 redis-cli/多语言SDK 交互

分布式数据库

解决容量问题

单体数据库系统单点容量有限,受硬件限制

分布式数据库:存储节点池化,动态扩缩容

解决弹性问题

单体数据库升级扩容,需要停机,影响业务

分布式数据库:存储节点和CPU池化,动态扩缩容

问题

单写多写

从磁盘弹性到内存弹性

分布式事务优化

新技术演进

软件架构变更

Bypass OS kernel:绕过操作系统内核,直接访问硬件

SPDK:Storage Performance Development Kit

  1. 通过用户态驱动程序,绕过操作系统内核,直接访问硬件
  2. 用轮询代替中断
  3. 无锁化设计,降低开销

AI

数据存储格式转换

新硬件革命

  1. RDMA:Remote Direct Memory Access,远程直接内存访问,绕过操作系统内核,直接访问内存
  2. Persistent Memory:持久内存,介于内存和SSD之间,速度比SSD快,容量比内存大
  3. 可编程交换机:P4,可编程数据包处理器,可编程交换机,可编程网卡,可编程存储控制器
  4. CPU/GPU/DPU:越来越强的计算能力(DPU:异构计算,减轻CPU负担)

gorm

迭代十年的ORM框架,支持主流数据库,支持事务,支持连接池,支持链式操作,支持预加载,支持事务

gorm 基础使用

  1. 定义 gorm model 结构体
  2. 为 model 定义表名
  3. 连接数据库
  4. 增删查改

gorm 使用驱动连接数据库,支持主流数据库,如 mysql,postgres,sqlite,sqlserver,oracle

创建数据

// 创建数据
db.Create(&User{Name: "Jinzhu", Age: 18, Birthday: time.Now()})

如何使用 Upsert,使用OnConflict处理数据冲突

// Upsert
db.Clauses(clause.OnConflict{DoNothing: true}).Create(&user)

如何使用默认值

// 默认值
type User struct {
  gorm.Model
  Name         string `gorm:"default:'noname'"`
  Age          int64
}

查询数据

// 查询数据
var user User
db.First(&user, 1) // 查询id为1的product
db.First(&user, "name = ?", "jinzhu") // 查询code为l1212的product

First 使用踩坑:查询不到数据时返回 ErrRecordNotFound 错误,查询多条数据不会返回错误

使用结构体为查询条件:指挥查询非零值字段,可以使用map构建查询条件

更新数据

// 更新数据
db.Model(&user).Update("name", "jinzhu")

// 更新多个字段
db.Model(&user).Updates(User{Name: "jinzhu", Age: 18}) // 仅更新非零值字段

// 使用map更新多个字段
db.Model(&user).Updates(map[string]interface{}{"name": "jinzhu", "age": 18})

// 更新选定字段
db.Model(&user).Select("name").Updates(map[string]interface{}{"name": "jinzhu", "age": 18})

// gorm 表达式
db.Model(&user).Select("age").Updates(map[string]interface{}{"age": gorm.Expr("age * ? + ?", 2, 100)})

删除数据

// 物理删除
db.Delete(&user)

// 软删除
type User struct {
  gorm.Model
  Name         string
  Age          int64
  DeletedAt    gorm.DeletedAt
}

db.Delete(&user)

被软删除的数据不会被查询到,会将DeletedAt置为当前时间,可以使用 Unscoped 方法查询到

gorm 事务

Gorm 使用了 Begin 方法开启事务,使用 Commit 方法提交事务,使用 Rollback 方法回滚事务

// 开启事务
tx := db.Begin()
// 事务操作
tx.Create(...)
tx.Update(...)
tx.Delete(...)
// 回滚事务
tx.Rollback()
// 提交事务
tx.Commit()
// 自动提交事务
var err Error
tx := db.Begin()
tx.Create(...)
if err = tx.Transaction(func(tx *gorm.DB) error {
  tx.Update(...)
  tx.Delete(...)
  return nil
}); err != nil {
  tx.Rollback()
}

gorm hook

gorm 提供了 hook 方法,可以在创建、更新、删除、查询时执行自定义的方法, 且可以在 hook 方法中返回错误,自动回滚

// 创建前
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
  u.ID = uuid.New().String()
  return
}

性能提高

对于写操作,为了确保数据完整性,gorm 会将他们封装事务内运行,可以使用skipDefaultTransaction 关闭

使用 PrepareStmt 预编译 SQL 语句