这是我参与「第三届青训营 -后端场」笔记创作活动的的第5篇笔记
讲师介绍
课程目录
理解database/sql
目标:通过统一接口,操作不同数据库,不同数据库实现标准库接口,就可以统一访问
基本用法
用法:怎么建立连接,怎么实现连接去查询数据,怎么把数据scan到对象中去,然后处理错误
一行一行看例子,mysql包只是实现接口,怎么去连接和解析是黑盒的不用管
第四行需要不同数据库实现自己的driver,里面包含了怎么连接解析数据库,第八行实现dsn连接
dsn数据源信息:这里指mysql数据源
driver把需求发给数据库,处理返回数据rows,需要close.rows防止信息泄露
把相关数据scan到user中,然后把结果append
rows是游标,next不断获取下调数据,用close担心异常和panic导致rows泄露
可能产生的问题:事务的问题,row忘记关闭
多多看源码 rows.close可能会产生问题,通过next错误会消失,所有换成defer去查
经常会有一些不是数据相关的错误,都会返回error
完整的例子,可以换别的方式去实现其他数据库
设计原理
采用极简接口设计原则,对上层应用程序提供标准api接口,下层驱动暴露简单的驱动接口,在database包内部实现连接池的管理
连接池就是池化技术,请求量过大时,用池化技术去优化相关性能,费时的资源放到池子里。 例如维护连接数和阻塞队列,方便在池子取值对应。
DB对象实现,最重要的一部分就是连接池的部分,定义了常见参数,maxopen等
对连接池的管理两个方法,一个是配置,一个是取出状态进行连接池管理
定义常见参数,具体细节不用看,需要掌握方法
操作过程的伪实现,for循环,默认badconnretiries尝试两次连接,如果从连接池获得连接,也会有两只策略,如果原来有链接就直接连,不然就是新建,defer操作之后放回连接池,放的过程中会有校验工作,看是否满足连接池的状态需求,然后就是实现数据操作内容,执行sql,最后对于执行sql有误错误,如果时badconn,就会继续执行for循环,如果有一次没有错误就break
这是不能修改的,但是想一下,如果插入数据的时候,连接被数据库kill掉,数据的插入导致重复插入,会产生重复的问题,解决方案,可以知道badconntiries方法
怎么预留接口给予实现,接口支持传入dsn 返回一个连接
之前讲了database包的整体流程和连接池,接下来看怎么预留接口来对其他实现
实现register方法,把driver成为一个全局变量
先创建一个regiseter创建一个driver,然后open,
这个设计的问题:dsn是一个特别长的字符串,不能具体了解什么意思,有些参数不能懂,有些参数不能传入,字符串的转义也很难,对于用户定义的数据库密码转义一直不成功,import 没有编译检查,不会产生错误,register注册到map中,driver就会碰到重名问题,例如mysql名字重名,业务不能同时使用,import顺序也会引发初始化异常
第二个方法出来比较晚,新的接口传入一个interface,可以返回一个db
DB连接的类型,一个是简单连接,stmt,直接预备好
数据处理方法: 1执行sql,返回的数据是否成功
2,3把数据库请求的结果当作行返回
driver通过实现interface,通过协议连接的解析实现,通过next方法返回到目标值
具体实现数据库的值,driver通过实现interface,通过协议连接解析,把数据库返回的值通过next方法,目标值,通过操作结果返回到应用程序中
大概过了包的概览和基本用法,还有很多细节没有实现,可以学习别人解决问题的思路
GORM基础使用
基础用法
重复性SQL,由来,支付系统,需要大量业务逻辑要手拼sql,完成状态操作
简化了select,对于err判断变少了
基本用法看一下即可,怎么操作数据库,create可以拿到id等
简单用法,可以看gorm文档去看一些别的介绍
模型定义
模型定义介绍:列出了基本的类型,包提供了value和scan接口可以提现复杂操作,可以通过crud API进行操作
匿名嵌套,结果是一样的
表名,变量,主键,模型和数据库表的定义是能够对应的
关联介绍
o:object
r:Relation
m:Mapping
go提供了关联支持,
保存关联,如果有就更新,没有就创建
gorm的关联模式提供了相关方法,这里面有很多方式如图
可以对单独用户操作,也可以批量操作
查关联,可以进行预操作
preload,在查询用户触发另一个sql,查询的用户就可以预加载orders和profile
join通过一条sql,把相关关联查出来
删除孤儿数据 通过数据库约束,删除的用户把相关全部删除
通过select的方式把相关关联删除
发现重复问题,抽象解决重复问题
GROM设计原理
一行配置提升性能:?最后揭晓
在database/sql上介绍,加了一层进行交互
SQL怎么生成的
找出所有超过35岁,并且不是manager
db对象执行任何方法都会产生gormstatement对象,做方法会用给gormsatament对象子句的方法就是chain method
看最后是find,select,updata,不写就是默认
gormsatament相关语句就是chainmethod,橙色是必备的子句,其他不写就是默认
GORM参考了select的过程,来写
WHERE把接受到的参数,调用getInstance,再把它添加到,翻译成go的子句然后放到tx.statement子句
finish mehtod就是find拿到这些参数,然后目标值加到statement里面,然后取用gorm里的query callback执行里面的statement,取出callback是process,
go里面callback,当前的statement返回processor,processor支持的子句的情况一一翻译出来,然后放到build方法,编译,再交给connpool执行
lock的形式与mysql8不一样,初始化db的实话来看版本,然后对应版本初始化参数,根据参数返回builder,灵活扩展数据库支持
简化版本,会先发请求,然后初始化一些参数,根据参数不同来决定
以hint包介绍,知道gorm的Statment是由很多子句构成,然后根据当前finsh类型提供方法
通过扩展子句加速查询,gostatement是很多语句构成,根据当前的statement子句,来形成最后的句子,就是拓展子句
第一个例子,select后面加了评论,就能注册一个查询优化器,生成一个带查询优化器的sql
第二组例子,指定索引然后加快操作,通过force index加快查询索引
第三组例子 自由指定,来拓展语句,需要对什么子句处理
不同数据库删除时是不一样的,对于第一个删除就是直接删除
对于mysql还支持了别的
迁移问题?隐藏差异,不改变代码,就能够迁移
对应gorm,在不改变代码支持各种数据库,最优选择子句的模式,不同的数据库,实现driver,自定义支持不同子句类型,生成不同的sql,配合子句的标的形式,生成专门的sql,通过自由选择clause来
不同模式由不同的callbacks
create的callback如下
一个是creat的时候开创一个事务,如果没有事务内,只会用在当前事务内,第二个是取出当前model的方法,吧这些方法一一执行返回结果,第三个保存前置关联,第四个直接执行creat,第五个把后置关联保存,
具体例子,把插件系统所有create方法逐步取出来,一一调用,完成上面所有操作
处理不仅仅是变成sql,也可以变成从redis取缓存的操作,执行中间件的服务
创建新方法,是放在最后执行的,也可以用remove等
自由定制和扩展
系统可能会有很多租户,把不同租户的数据放在一起是不同意的,想要在查询的时候直接保持
查询数据和租户绑定,通过setid设置,注册了新的callback,都会加settenantscale,都加了过滤条件
三个数据库,主数据库是写的数据库,从数据库是读数据库
第二组条件只设置从数据库,只会使用当前db的主连接
第三组条件 设置db6 db7
通过子句来表示
connnpool是什么
重新定义接口,实现了connpool接口来和数据库交互
读写分离好处,把一个写的dbconn链接替换connpool,执行findquery,用读数据库链接替换,如果是事务会继续保持事务
如果当前是事务,会直接使用事务
在开启这个模式后,所有的sql语句交给connpool预编译,然后缓存起来,然后对于同类sql进行执行
全局模式就是所有都会预编译,不会包含参数,只有整体
整数是有限制的,写sql的时候,写sql的时候一定要作为参数输入
会话模式只会对于当前会话执行
海外国家的数据安全,所有数据只能先存储到自己国家,再存到自己,只需要根据gorm的业务层多写插件
先把数据发送到该国数据库,然后再同步,只需要配置参数即可完成
可以获取api返回的值,也可以做缓存
这里预编译到sql数据库会变慢,用完就扔掉,前面是可以缓存下来,用来后面加速
开发bytedgorm实现了默认配置,根据dialector来实现的
缓存插件
不同组来定义链接,实现接口
GORM最佳实践
以下讲了reference
最佳实践
自定义序列化,对数据库层处理逻辑进行一些序列化
Q & A
Q:go支持动态sql? A:支持,所有的sql都是,通过方法判断来生成statement,也可以把代码封装
Q:请问Gorm默认的CreateAt在Mysql中对应什么类型 A:datatime
Q:外键的使用场景?如果直接通过软件逻辑来实现多次SQL查询合适吗? A:数据库侧的外键保证一致性,gorm的外键和数据库不一样,是简单字段,可以作为id查找关联依赖
Q:师请问如果项目中间想要更改数据库的结构,是直接更改对应model的字段吗?如果增加了某个约束,但数据库中的目前数据并不符合这个约束,gorm会怎样处理呢?实际中应该怎样处理呢? A:如果要修改数据库结构,要改sturct结构,也可以迁移数据,如果增加了约束,需要修改数据,不然增加不了约束。
Q:如果点赞这种操作,在数据库中存储的点赞关系,取消赞的话,直接删除这条数据好,还是说添加一个注销标记好 A:倾向于保留数据,然后判断时间什么时候cancel的
Q:对于频繁的计数操作,涉及到总数据量的变化。使用MYSQL的条件下,是否有比较优雅或更高性能的操作呢?还是需要引入Reids等内存数据库 A:引入redis数据来解决
Q:老师,新手也想参与贡献gorm的代码有什么推荐方案! A:从gorm的当前问题入手,面向问题是什么,尝试解决用户问题
Q:关联删除能保证事务嘛 刚才看到shi li代码没有使用事务 A: 默认是开启,如果没有关闭时数据是一致的,或者在会话关闭
Q:实际业务中什么时候使用关系型数据库 什么时候使用对象型或者其他数据库呢 A:看实际业务出发,对关联要求叫多,用关系型数据,如果对查询较高等,需要从实际业务出发
Q:为什么我看到一些insert操作也有人用.Save 不是用.Create吗 有什么区别 A:save做insert会判断主键有没有赋值,有了就用update,要用create去做,不明确和数据并发不明确用sava
Q:请问老师,如何从gorm v1平滑过渡到v2?对于开源项目,不同版本之间的迭代如何设计成用户升级友好型呢? A:写大版本的时候,80%,90%平滑迁移了,但是小部分做了迁移
Q:MySQL的索引使用的是高阶B+树还是低阶B+树啊。如果是3阶4阶的低阶树,每个索引节点都用一个数据页存储是不是太浪费了,因为一个数据页16K,而一个索引可能就十几B,但使用高阶可能是几百阶的B+树,会不会节点内的顺序查找太久了 A:后面又mysql课程介绍
Q:请问,结构体里想加入嵌入的结构体,作为输出用(例如,uservo里放了一个videovo),通过gorm2预加载写了语句:db.preload("VideoVo").Raw(sql).scanf(&user),但返回的user内嵌的结构体字段还是没赋值上,这是为什么呀? A:prelode不支持raw sql
Q:业务代码一般是通过model(和表对应的结构体)查询出来值,再赋值到返回给前端的响应体结构(vo)中吗 A:把前端api返回定义结构,gorm有个smart select可以去选择需要的字段,减少负担