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

网友投稿 1083 2022-11-27

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

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

前言

本次课程内容十分的底层,GORM作者张金柱老师介绍了Go语言原生​​database/sql​​的实现原理和使用方式,并且进一步给出了自己设计GORM框架的出发点和一些实现细节。我感受深刻,也看到了技术人的热情。

仰望星空,脚踏实地,本篇笔记暂时的定位是以介绍两种方式操作(MySQL)数据库为主,并辅以一些源码的解读帮助大家更好理解数据库连接的过程。

我笔记中连接的数据库是使用docker容器部署的MySQL实例,各位也可以根据自己情况而定,换成本地也是很方便的。

docker的学习可以参看这篇文章:​​juejin-/post/706402…​​

一、database/sql的基础使用

英文手册:​​Go database/sql tutorial​​

1.1 连接数据库

这里​​import​​​了两个包,一个是​​database/sql​​​,这个包是Go原生的负责数据库操作的包,定义了很多连接和操作数据库实例的接口(定义了很多方法)。另一个包是数据库提供商负责提供的数据库驱动包​​github.com/go-sql-driver/mysql​​​,这个包去实现了Go原生​​database/sql​​包中接口定义的方法(实现了操作某数据库实例的具体细节)。

这样对于Go用户来说,我只要调用我接口定义的方法,​​import​​入MySQL提供的driver包,那么我就无须关注底层的具体实现,从而可以使用Go的API与MySQL进程进行通信,发送CRUD请求,然后再由MySQL进程去管理MySQL磁盘文件,持久化数据变更。

这里留意上图我给出的一些注释,它们来自源码包中的对应方法的注释,以​​sql.Open()​​方法为例,它说了Open方法可能只是会校验参数的语法而非真正建立连接,为了验证数据源的有效性可以用Ping方法,并且第二个红框中介绍到返回的DB引用是并发安全的,而作为一个数据库操作服务一般也是会不断给前端提供CRUD服务,因此它说很少需要关闭一个DB(等需要关闭的时候,服务也就挂了)

也可以再看一下sql.DB的结构体,它维护着所连接的数据对象,持有一个连接池,维护很多的连接,并且有很多参数项目:如​​最大连接数​​​,​​最大闲置连接数​​等等。(DB是并发安全的,因为并发进来的连接都要与其通信交互)

1.2 新建数据库

接下来从新建数据库开始使用Go原生database/sql包体验一下CRUD

-- ------------------------------ Table structure for user-- ----------------------------DROP TABLE IF EXISTS `user`;CREATE TABLE `user` ( `id` int NOT NULL AUTO_INCREMENT COMMENT 'id', `name` varchar(255) NOT NULL COMMENT '姓名', `age` int NOT NULL COMMENT '年龄', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb3;-- ------------------------------ Records of user-- ----------------------------BEGIN;INSERT INTO `user` VALUES (1, '小明', 12);INSERT INTO `user` VALUES (2, '小红', 15);INSERT INTO `user` VALUES (3, '小蓝', 11);COMMIT;

1.3 查询

单行查询

func getUserById(id int) error { sqlStr := "select id, name, age from user where id = ?" var user User // 这里的1是参数,用于查询id=1的行记录 if err := db.QueryRow(sqlStr, 1).Scan(&user.id, &user.name, &user.age); err != nil { return err } fmt.Println(user.id, user.name, user.age) return nil

多行查询

func queryUsers() error { sqlStr := "select id, name, age from user where id > ?" rows, err := db.Query(sqlStr, 1) if err != nil { return err } // 关闭rows释放持有的数据库链接 defer rows.Close() // 循环读取结果集中的数据 for rows.Next() { var user User if err := rows.Scan(&user.id, &user.name, &user.age); err != nil { return err } fmt.Printf("id:%d name:%s age:%d\n", user.id, user.name, user.age) } return nil

1.4 插入

这里要说明一下,下面的插入、更新、删除使用到的API是同一个:​​db.Exec​​,args参数是任意个占位参数。

func insertUser() error { sqlStr := "insert into user(name, age) values (?,?)" ret, err := db.Exec(sqlStr, "小黄", 17) if err != nil { return err } theID, err := ret.LastInsertId() // 新插入数据的id if err != nil { return err } fmt.Printf("insert success, the id is %d.\n", theID) return nil

1.5 更新

func updateUser() error { sqlStr := "update user set age=? where id = ?" ret, err := db.Exec(sqlStr, 20, 3) if err != nil { return err } n, err := ret.RowsAffected() // 操作影响的行数 if err != nil { return err } fmt.Printf("update success, affected rows:%d\n", n) return nil

1.6 删除

// 删除数据func deleteUser() error { sqlStr := "delete from user where id = ?" ret, err := db.Exec(sqlStr, 5) if err != nil { return err } n, err := ret.RowsAffected() // 操作影响的行数 if err != nil { return err } fmt.Printf("delete success, affected rows:%d\n", n) return nil

最后放一下整体的main函数调用流程

二、GORM的基础使用

Go英文手册:​​gorm.io/docs​​

Go中文手册:​​learnku.com/docs​​

原本我尝试按照文档做了一些操作的案例,但中途认为,这样做的意义并不是很大,加上上面已经罗列了使用原生database/sql进行CRUD的步骤。GORM文档已经十分清爽完备,最终决定不再笔记中二次赘述。

小结

GORM还是原生database/sql,都是在应用程序层面,向用户隐藏了底层连接数据库,操作数据库的实现细节。使得开发者通过API就可以实现和数据库进程的通信,而数据库进程才是直接操作数据库文件的,而非应用程序。

或者说应用程序进程的工作只是将SQL命令传递给了数据库进程,最终一条SQL的执行将完全在数据库进程内完成。以MySQL为例收到一条SQL后将涉及如下方面的工作:

语句的解析查询的优化存储引擎的工作:

索引的选择与实现数据一致性的保证(redo log)事务的实现,不同事务隔离级别如何实现(多版本并发控制MVCC、undo log)锁的实现(表级锁、行级锁、间隙锁)

数据恢复与同步,主从复制(bin log)等等...

上面罗列的这些条目在运行时会产生很多业务相关的问题,由此衍生出很多MySQL性能优化的技巧,常见的如索引如何设定可以提升查询效率,慢SQL如何排错,为什么我的索引失效了,事务如何使用并发性能会更高等等,这些技巧或许在后续的项目实践中可以使用到,也希望大家关注。

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:高性能Go语言发行版优化与落地实践|青训营笔记
下一篇:Kitex源码阅读——脚手架代码是如何通过命令行生成的(二)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~