go和数据库
经典案例
数据产生
如一个注册新帐号的操作
数据流动
一条用户注册数据 -> 后端服务器 -> 数据库系统 -> 其他非存储系统
数据持久化
数据到达数据库系统
潜在问题
存储 & 数据库系统
存储系统:一个提供读写,控制类接口,能安全有效把数据持久化的软件,即存储系统
用户,硬盘,内存,网络
存储系统特点
存储器层级结构
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
AI
数据存储格式转换
新硬件革命
gorm
迭代十年的ORM框架,支持主流数据库,支持事务,支持连接池,支持链式操作,支持预加载,支持事务
gorm 基础使用
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 语句