两个概念

(database-driver)
(driver-manager)

本文以mysql数据库为例。

mysql-driver
database-driver
driver-managerdatabase/sqldriver-manager
managersql.driver

第一部分,深入了解一下database/sql

举个栗子,现代数据库多种多样,mysql,sql_server,oracle等等,各个厂商的数据库各异,编程语言总不能为每一个数据库都写一个特定的管理系统吧(那得多麻烦)。

如果是这样的话,那么编程语言得多么臃肿啊,对程序猿也不友好,要掌握那么多的数据库驱动规则(这谁顶得住啊!)

我语言(go语言)如果自定义一套标准的接口(上图database/sql),那么数据库厂商按照我这个提供的这个接口写一个驱动来适配这个接口(这个接口可以与go程序进行交互),那么语言只要制定一套标准不显得臃肿,程序员也可以只要掌握这个接口的使用就可以了(矛盾转移,将任务堆给数据库厂商)

上图可示database/sql接口包括两个层面:

1.面向应用的api,供程序员调用

2.面向数据库的api,供开发厂商开发数据的驱动程序

ok,开始下定义,第一部分完结~:

database/sqljdbc


第二部分,程序与数据库交互

要想连接到sql数据库,首先需要加载目标数据库的驱动,驱动里面包含着与该数据库交互的逻辑

那么如何加载目标数据库驱动?

sql.Registerdriver.Driversql.Register("sqlserver",&drv{})
initinitsql.register
database/sql


现在我们看看mysql驱动里面干了啥

initdatabase/sqlregister
goSDK/database/sql
Registerdriversinit
MySQLDriver


OK,这个就是mysql这个驱动包与database/sql包交互的第一层逻辑

database/sqlregistersqldrivers


OK,mysql驱动就讲这么多,现在我们回到Go中sql接口层面,毕竟这才是我们需要接触和掌握的

程序中操作数据库的第一行代码为

那么Open干了啥,当然要从源码入手啦

OpendriverNamedriversinit
OpenDB()driver.Connector
OpenDB
// OpenDB may just validate its arguments without creating a connection
// to the database. To verify that the data source name is valid, call
// Ping.


// The returned DB is safe for concurrent use by multiple goroutines
// and maintains its own pool of idle connections. Thus, the OpenDB
// function should be called just once. It is rarely necessary to
// close a DB.


// DB is a database handle representing a pool of zero or more
// underlying connections. It's safe for concurrent use by multiple
// goroutines.

意思是:

Open()
sql.DBOpen()sql.DB

<画重点>sql.DB是用来操作数据库的,它代表了0个或者多个底层连接的池,这些连接由sql包来维护,sql会自动的创建和释放这些连接,它对于多个goroutine并发的使用是安全的。

DBDB

那么啥是连接池呢,为啥要有连接池?

这是我之前写过的一个tcp高并发服务器demo,里面的工作池与连接池类似。一个任务进来,你要开启一个协程去处理这个任务。那么你不能无限开协程啊,无限开个1千万?1e个协程?那你内存不得挂掉嘛?因此如果我们在处理任务前,制作一个池子里面分配适合自己电脑配置的处理任务协程数量,那么任务进来就丢入这个池子中,负载均衡地放入里面的处理任务协程队列中去处理。这样才不会造成熵增嘛~

第三部分,介绍完了Open()函数,该介绍查询方法啦

Open()

那如何测试连接是否成功呢?

func(db *DB)PingContext()
Context
contextcontext.Background()ContextContext
context


由输出可以得出数据库连接成功。

Tiny tip,连接数据库前先保证mysql服务是启动的

开启mysql服务 net start mysql

mysql -u root -p 进入mysql

sql.DB类型上用于查询的方法有

Query
QueryRow

与这两个配套的方法是加上 上下文,可以在查询的时候带上 上下文,如截止时间,取消操作,或者所需要的值

QueryContext
QueryRowContext


1·func (db DB) Query(query string, args ...interface{}) (Rows, error)

返回的结果是type Rows struct{... ...}

现在我们在源码中康康Rows它有哪些方法(一定要学会看源码!!!)

Scan将数据库中当前行的数据拷贝到dest所指向的值中,dest中的值个数必须与Rows中的列数相同。

Scan将从数据库读取的列转换为以下常见的Go类型和sql包提供的特殊类型:

在最简单的情况下,如果源列的值类型是整型,bool或字符串类型T, dest类型*T, Scan只需通过指针赋值......

ColumnTypes返回列的类型、长度、是否可为空等信息(即建表时设置的列的属性)。

Columns简单地返回所有的列名,如果在关闭的时候出现错误将返回一个错误

遍历结果集,每次读取结果的一行,返回的结果为true表明还有数据,否之表示读到结果的末尾

2.func (db DB) QueryRow(query string, args ...interface{}) Row

QueryRow返回的是Row,注意和Query不一样啊,人家是Rows,多了个ssssss,那么Row这个方法就简单多了(可以少码很多了ye)

也是从结果中拷贝数据到后面跟着的dest中去,还要一个err方法我就不讲了。

3当然DB还对应很多方法

https://pkg.go.dev/database/sql#DB

第四部分,实战

先理清逻辑。

Open()*DBDB
QueryRowRowRowScan

OK,Talk is free,Show me the code

在model层中定义一个与数据库中字段匹配的结构体,它用来存储scan读出的数据

services层中书写与数据交互的代码。

入口main函数


打印输出的运行结果


打开Navicat工具验证,与数据库中的数据是一致的

OK,第四部分完结,收工睡觉~