Go程序中操作MySQL数据库

作为一个curdboy,当然必须学会如何操作数据库了。

go官⽅仅提供了database包,database包下有sql/driver。该包⽤来定义操作数据库的接⼝,这保证了⽆论使⽤哪种数据库,他们的操作⽅式都是相同的。 ​

但go官⽅并没有提供连接数据库的driver,如果要操作数据库,还需要第三⽅的driver包github.com/go-sql-driver/mysql




先定义一张表,在之后的讲解中的所有例子,都是基于这张表做的演示,那么就定义一张最常见的用户表吧。

CREATE TABLE `user` (
    `id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
    `name` VARCHAR(45) COLLATE UTF8MB4_UNICODE_CI NOT NULL,
    `pwd` VARCHAR(255) COLLATE UTF8MB4_UNICODE_CI NOT NULL,
    `created_at` BIGINT(20) UNSIGNED NOT NULL,
    PRIMARY KEY (`id`),
    KEY `idx_created_at` (`created_at`)
)  ENGINE=INNODB DEFAULT CHARSET=UTF8MB4 COLLATE = UTF8MB4_UNICODE_CI;

插入几条数据,方便之后快乐的玩耍

INSERT INTO `user` (`id`, `name`, `pwd`, `created_at`) VALUES ('1', '牛A', '555', '1559318400');
INSERT INTO `user` (`id`, `name`, `pwd`, `created_at`) VALUES ('2', '牛B', '555', '1559318400');
INSERT INTO `user` (`id`, `name`, `pwd`, `created_at`) VALUES ('3', '牛C', '555', '1573441871');
INSERT INTO `user` (`id`, `name`, `pwd`, `created_at`) VALUES ('4', '牛X', '555', '1558329240');

在这里插入图片描述
在这里插入图片描述


import

Golang 提供了database/sql包,⽤于对SQL数据库的访问。

它提供了⼀系列接⼝⽅法,⽤于访问关系数据库。它并不会提供数据库特有的⽅法,那些特有的⽅法交给数据库驱动去实现。

匿名导包——只导⼊包但是不使⽤包内的类型和数据。使⽤匿名导包的⽅式(在包路径前添加下划线 “_” )导⼊MySQL驱动。

匿名导⼊的包与其他⽅式导⼊包⼀样,会让导⼊包编译到可执⾏⽂件中。通常来说, 导⼊包后就能调⽤该包中的数据和⽅法。

但是对于数据库操作来说,我们不应该直接使⽤导⼊的驱动包所提供的⽅法,⽽是应该⽤ sql.DB对象所提供的统⼀的⽅法。因此在导⼊ mysql 驱动时,使⽤了匿名导⼊包的⽅式。

当导⼊⼀个数据库驱动后,该驱动会⾃⾏初始化并注册到Golang的database/sql上下中,这样就可以通过database/sql 包所提供的⽅法来访问数据库了。

import (
	"database/sql"
	_ "github.com/go-sql-driver/mysql"
)



Open

sql.Open()函数返回sql.DB对象。作为操作数据库的⼊⼝对象sql.DB, 主要为我们提供了两个重要的功能。

sql.DB 通过数据库驱动为我们提供管理底层数据库连接的打开和关闭操作。sql.DB 为我们管理数据库连接池。正在使⽤的连接被标记为繁忙,⽤完后回到连接池等待下次使⽤。所以,如果你没有把连接释放回连接池,会导致过多连接使系统资源耗尽。

sql.Open()返回的sql.DB对象是协程并发安全的。sql.DB的设计就是⽤来作为⻓连接使⽤的。不要频繁Open, Close。⽐较好的做法是,为每个不同的datastore建⼀个DB对象,保持这些对象Open。如果需要短连接,那么把DB作为参数传⼊function,⽽不要在function中Open, Close。

	dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", "root", "123456", "127.0.0.1", "3306", "test")
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		return
	}
	defer db.Close()



Prepare

预编译语句(PreparedStatement)提供了诸多好处, 因此我们在开发中尽量使⽤它。

PreparedStatement 可以实现⾃定义参数的查询。PreparedStatement 通常来说, ⽐⼿动拼接字符串 SQL 语句⾼效。PreparedStatement 可以防⽌SQL注⼊攻击,⼀般用Prepared Statements和Exec()完成INSERT, UPDATE,DELETE操作。

	stmt, err := db.Prepare(`INSERT INTO 'user' ('id', 'name', 'pwd', 'created_at') VALUES (?, ?, ?, ?)`)
	if err != nil {
		return
	}
	defer stmt.Close()
	
	if _, err := stmt.Exec(5, "牛Z", "555", "1558329240"); err != nil {
		return
	}



Scan

查询数据时需要注意的细节
rows.Scan()⽅法的参数顺序很重要,必须和查询结果的column相对应(数量和顺序都需要⼀致)。不然会造成数据读取的错位。

因为golang是强类型语⾔,所以查询数据时先定义数据类型。

查询数据库中的数据存在三种可能:存在值、存在零值、未赋值NULL三种状态,因此可以将待查询的数据类型定义为sql.NullString 、sql.NullInt64类型等。可以通过判断Valid值来判断查询到的值是否为赋值状态还是未赋值NULL状态。

每次db.Query操作后, 都建议调⽤rows.Close()。因为 db.Query() 会从数据库连接池中获取⼀个连接, 这个底层连接在结果集(rows)未关闭前会被标记为处于繁忙状态。当遍历读到最后⼀条记录时,会发⽣⼀个内部EOF错误,⾃动调⽤rows.Close()。但如果出现异常,提前退出循环,rows不会关闭,连接不会回到连接池中,连接也不会关闭, 则此连接会⼀直被占⽤。因此通常使⽤ defer rows.Close() 来确保数据库连接可以正确放回到连接池中。

阅读源码发现rows.Close()操作是幂等操作,⽽⼀个幂等操作的特点是:其任意多次执⾏所产⽣的影响与⼀次执⾏的影响相同。所以即便对已关闭的rows再执⾏close()也没关系。

	queryStmt, err := db.Prepare(`select 'id', 'name', 'pwd', 'created_at' from 'user'`)
	if err != nil {
		return
	}
	defer queryStmt.Close()
	
	rows, err := queryStmt.Query()
	if err != nil {
		return
	}
	defer rows.Close()

	users := make([]*user, 0)
	for rows.Next() {
		user := &user{}
		if err := rows.Scan(&user.Id, &user.Name, &user.Pwd, &user.CreatedAt); err != nil {
			return
		}
		users = append(users, user)
	}



巨人的肩膀

从他人的工作中汲取经验来避免自己的错误重复,正如我们是站在巨人的肩膀上才能做出更好的成绩。

https://github.com/rubyhan1314/Golang-100-Days



VChat

一个没有哆啦A梦和静香的IT码农,不专业Gopher
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值