第五节设计模式之 Database/SQL 与 GORM 实践 | 青训营笔记

235 阅读12分钟

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

讲师介绍

image.png image.png

课程目录

image.png

理解database/sql

image.png 目标:通过统一接口,操作不同数据库,不同数据库实现标准库接口,就可以统一访问

基本用法

image.png 用法:怎么建立连接,怎么实现连接去查询数据,怎么把数据scan到对象中去,然后处理错误 image.png 一行一行看例子,mysql包只是实现接口,怎么去连接和解析是黑盒的不用管
第四行需要不同数据库实现自己的driver,里面包含了怎么连接解析数据库,第八行实现dsn连接
dsn数据源信息:这里指mysql数据源 image.png driver把需求发给数据库,处理返回数据rows,需要close.rows防止信息泄露 image.png 把相关数据scan到user中,然后把结果append rows是游标,next不断获取下调数据,用close担心异常和panic导致rows泄露
可能产生的问题:事务的问题,row忘记关闭
多多看源码 rows.close可能会产生问题,通过next错误会消失,所有换成defer去查 image.png 经常会有一些不是数据相关的错误,都会返回error image.png 完整的例子,可以换别的方式去实现其他数据库

设计原理

image.png 采用极简接口设计原则,对上层应用程序提供标准api接口,下层驱动暴露简单的驱动接口,在database包内部实现连接池的管理
连接池就是池化技术,请求量过大时,用池化技术去优化相关性能,费时的资源放到池子里。 例如维护连接数和阻塞队列,方便在池子取值对应。 image.png DB对象实现,最重要的一部分就是连接池的部分,定义了常见参数,maxopen等
对连接池的管理两个方法,一个是配置,一个是取出状态进行连接池管理 image.png 定义常见参数,具体细节不用看,需要掌握方法 image.png image.png 操作过程的伪实现,for循环,默认badconnretiries尝试两次连接,如果从连接池获得连接,也会有两只策略,如果原来有链接就直接连,不然就是新建,defer操作之后放回连接池,放的过程中会有校验工作,看是否满足连接池的状态需求,然后就是实现数据操作内容,执行sql,最后对于执行sql有误错误,如果时badconn,就会继续执行for循环,如果有一次没有错误就break
这是不能修改的,但是想一下,如果插入数据的时候,连接被数据库kill掉,数据的插入导致重复插入,会产生重复的问题,解决方案,可以知道badconntiries方法 image.png 怎么预留接口给予实现,接口支持传入dsn 返回一个连接 image.png 之前讲了database包的整体流程和连接池,接下来看怎么预留接口来对其他实现
实现register方法,把driver成为一个全局变量 image.png 先创建一个regiseter创建一个driver,然后open,
这个设计的问题:dsn是一个特别长的字符串,不能具体了解什么意思,有些参数不能懂,有些参数不能传入,字符串的转义也很难,对于用户定义的数据库密码转义一直不成功,import 没有编译检查,不会产生错误,register注册到map中,driver就会碰到重名问题,例如mysql名字重名,业务不能同时使用,import顺序也会引发初始化异常 image.png image.png 第二个方法出来比较晚,新的接口传入一个interface,可以返回一个db image.png DB连接的类型,一个是简单连接,stmt,直接预备好 数据处理方法: 1执行sql,返回的数据是否成功
2,3把数据库请求的结果当作行返回 image.png driver通过实现interface,通过协议连接的解析实现,通过next方法返回到目标值
具体实现数据库的值,driver通过实现interface,通过协议连接解析,把数据库返回的值通过next方法,目标值,通过操作结果返回到应用程序中 image.png 大概过了包的概览和基本用法,还有很多细节没有实现,可以学习别人解决问题的思路

GORM基础使用

image.png

基础用法

image.png 重复性SQL,由来,支付系统,需要大量业务逻辑要手拼sql,完成状态操作 image.png image.png 简化了select,对于err判断变少了 image.png 基本用法看一下即可,怎么操作数据库,create可以拿到id等 image.png 简单用法,可以看gorm文档去看一些别的介绍

模型定义

image.png 模型定义介绍:列出了基本的类型,包提供了value和scan接口可以提现复杂操作,可以通过crud API进行操作 image.png 匿名嵌套,结果是一样的 image.png 表名,变量,主键,模型和数据库表的定义是能够对应的 image.png

关联介绍

image.png o:object r:Relation m:Mapping go提供了关联支持, image.png 保存关联,如果有就更新,没有就创建
gorm的关联模式提供了相关方法,这里面有很多方式如图
可以对单独用户操作,也可以批量操作 image.png 查关联,可以进行预操作
preload,在查询用户触发另一个sql,查询的用户就可以预加载orders和profile join通过一条sql,把相关关联查出来 image.png 删除孤儿数据 通过数据库约束,删除的用户把相关全部删除
通过select的方式把相关关联删除 image.png 发现重复问题,抽象解决重复问题

GROM设计原理

image.png 一行配置提升性能:?最后揭晓 image.png 在database/sql上介绍,加了一层进行交互 image.png

SQL怎么生成的

image.png 找出所有超过35岁,并且不是manager image.png db对象执行任何方法都会产生gormstatement对象,做方法会用给gormsatament对象子句的方法就是chain method 看最后是find,select,updata,不写就是默认
gormsatament相关语句就是chainmethod,橙色是必备的子句,其他不写就是默认 GORM参考了select的过程,来写 image.png WHERE把接受到的参数,调用getInstance,再把它添加到,翻译成go的子句然后放到tx.statement子句 image.png finish mehtod就是find拿到这些参数,然后目标值加到statement里面,然后取用gorm里的query callback执行里面的statement,取出callback是process, go里面callback,当前的statement返回processor,processor支持的子句的情况一一翻译出来,然后放到build方法,编译,再交给connpool执行 image.png image.png lock的形式与mysql8不一样,初始化db的实话来看版本,然后对应版本初始化参数,根据参数返回builder,灵活扩展数据库支持
简化版本,会先发请求,然后初始化一些参数,根据参数不同来决定 image.png 以hint包介绍,知道gorm的Statment是由很多子句构成,然后根据当前finsh类型提供方法
通过扩展子句加速查询,gostatement是很多语句构成,根据当前的statement子句,来形成最后的句子,就是拓展子句
第一个例子,select后面加了评论,就能注册一个查询优化器,生成一个带查询优化器的sql 第二组例子,指定索引然后加快操作,通过force index加快查询索引
第三组例子 自由指定,来拓展语句,需要对什么子句处理 image.png 不同数据库删除时是不一样的,对于第一个删除就是直接删除
对于mysql还支持了别的 迁移问题?隐藏差异,不改变代码,就能够迁移
对应gorm,在不改变代码支持各种数据库,最优选择子句的模式,不同的数据库,实现driver,自定义支持不同子句类型,生成不同的sql,配合子句的标的形式,生成专门的sql,通过自由选择clause来 image.png 不同模式由不同的callbacks image.png create的callback如下
一个是creat的时候开创一个事务,如果没有事务内,只会用在当前事务内,第二个是取出当前model的方法,吧这些方法一一执行返回结果,第三个保存前置关联,第四个直接执行creat,第五个把后置关联保存,
具体例子,把插件系统所有create方法逐步取出来,一一调用,完成上面所有操作
处理不仅仅是变成sql,也可以变成从redis取缓存的操作,执行中间件的服务 image.png 创建新方法,是放在最后执行的,也可以用remove等 image.png 自由定制和扩展 image.png 系统可能会有很多租户,把不同租户的数据放在一起是不同意的,想要在查询的时候直接保持 查询数据和租户绑定,通过setid设置,注册了新的callback,都会加settenantscale,都加了过滤条件 image.png 三个数据库,主数据库是写的数据库,从数据库是读数据库
第二组条件只设置从数据库,只会使用当前db的主连接
第三组条件 设置db6 db7
通过子句来表示

connnpool是什么

image.png image.png 重新定义接口,实现了connpool接口来和数据库交互 image.png 读写分离好处,把一个写的dbconn链接替换connpool,执行findquery,用读数据库链接替换,如果是事务会继续保持事务 image.png 如果当前是事务,会直接使用事务 在开启这个模式后,所有的sql语句交给connpool预编译,然后缓存起来,然后对于同类sql进行执行
全局模式就是所有都会预编译,不会包含参数,只有整体
整数是有限制的,写sql的时候,写sql的时候一定要作为参数输入
会话模式只会对于当前会话执行 image.png image.png 海外国家的数据安全,所有数据只能先存储到自己国家,再存到自己,只需要根据gorm的业务层多写插件
先把数据发送到该国数据库,然后再同步,只需要配置参数即可完成 image.png 可以获取api返回的值,也可以做缓存 image.png 这里预编译到sql数据库会变慢,用完就扔掉,前面是可以缓存下来,用来后面加速 image.png 开发bytedgorm实现了默认配置,根据dialector来实现的 image.png 缓存插件 image.png 不同组来定义链接,实现接口 image.png

GORM最佳实践

image.png 以下讲了reference

最佳实践

image.png image.png image.png image.png 自定义序列化,对数据库层处理逻辑进行一些序列化 image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png image.png

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可以去选择需要的字段,减少负担