database/sql

概念

database/sql

它的大致原理如下:

Driverdatabase/sql/driverDriverDriverdatabase/sqlsql.Register()database/sqldatabase/sqldatabase/sqlsql.Open()database/sqldatabase/sqlPrepare()Exec()Query()database/sql/driverdatabase/sqlPrepare()Exec()Query()database/sql
database/sql

特点

database/sql
database/sqldatabase/sqldatabase/sql

准备

database/sql

你可以使用 MySQL 命令行或图形化工具创建这张表。

连接数据库

database/sql
github.com/go-sql-driver/mysqldatabase/sql
sql.Open()
mysqldatabase/sqlgithub.com/go-sql-driver/mysqlsql.Registerdatabase/sql
initsql.Open()mysql
Data Source Name

下面是我们提供的 DSN 各部分解释:

user:passwordtcp(127.0.0.1:3306)127.0.0.13306/demodemocharset=utf8mb4UTF-8UTF-8UTF-8mb4parseTime=truedatetimetime.Timeloc=Local
sql.Open()*sql.DB
defer db.Close()database/sqldb
*sql.DBOpenClose*sql.DBOpenClose
sql.Open()
sql.Open()db.Ping()
sql.Open()database/sql

我们可以通过如下方法,控制连接池的一些参数:

这些参数设置可以根据经验来修改,以上参数能够满足一些中小项目的使用,如果是大型项目,则可以适当调高参数。

声明模型

模型
user

模型在 Go 中使用 struct 表示,结构体字段同数据库表中的字段一一对应。

其中 Salary 类型定义如下:

关于 Name、Salary 两个字段的特殊性,我将分别在 处理 NULL 和 自定义字段类型 部分讲解。

创建

*sql.DB 提供了 Exec 方法来执行一条 SQL 命令,可以用来创建、更新、删除表数据等。

这里使用 Exec 方法来实现创建一个用户:

首先我们实例化了一个 User 对象 user,并对相应字段进行赋值。

接着使用 db.Exec 方法来执行 SQL 语句:

其中 ? 作为参数占位符,不同数据库驱动程序的占位符可能不同,可以参考数据库驱动的文档。

我们将这 5 个参数顺序传递给 db.Exec 方法,即可完成用户的创建。

db.Exec 方法调用后将返回 sql.Result 保存结果以及一个 error 来标记错误。

sql.Result 是一个接口,它包含两个方法:

  • LastInsertId() (int64, error):返回新插入的用户 ID。
  • RowsAffected() (int64, error):返回当前操作受影响的行数。

接口具体实现有数据库驱动程序来完成。

调用 CreateUser 函数即可创建一个新的用户:

此外,database/sql 还提供了预处理方法 *sql.DB.Prepare 创建一个准备好的 SQL 语句,在循环中使用预处理,则可以减少与数据库的交互次数。

比如我们需要创建两个用户,则可以先使用 db.Prepare 创建一个 *sql.Stmt 对象,然后多次调用 *sql.Stmt.Exec 方法来插入数据:

db.Prepare 是预先将一个数据库连接和一个条 SQL 语句绑定并返回 *sql.Stmt 结构体,它代表了这个绑定后的连接对象,是并发安全的。

通过使用预处理,可以避免在循环中执行多次完整的 SQL 语句,从而显著减少了数据库交互次数,这可以提高应用程序的性能和效率。

使用预处理,会在 db.Prepare 时从连接池获取一个连接,之后循环执行 stmt.Exec,最终释放连接。

如果使用 db.Exec,则每次循环时都需要:获取连接-执行 SQL-释放连接,这几个步骤,大大增加了与数据库的交互次数。

不要忘记调用 stmt.Close() 关闭连接,这个方法是密等的,可以多次调用。

查询

现在数据库里已经有了数据,我们就可以查询数据了。

因为 Exec 方法只会执行 SQL,不会返回结果,所以不适用于查询数据。

*sql.DB 提供了 Query 方法执行查询操作:

db.Query*sql.Rows
rows.Next()forScan
rows.Next()true
rows.Scan()SELECT *user
rows.Scan()varcharstringinterror
CreatedAttime.Timesql.Open()parseTime=true
rows.Next()falseusers
rows.Err()
*sql.DBQueryRow
*sql.Row*sql.Rows
rows.Next()row.Sca()*sql.Row
row.Sca()sql.ErrNoRows
database/sql

可以按照如下方式判断特定的 MySQL 错误类型:

1045

以上代码可以改为:

row.Err()

更新

*sql.DB.Exec*sql.DB.ExecContext
ExecContextExeccontext.Context
res.RowsAffected()
res.RowsAffected()

删除

*sql.DB.ExecContext

事务

database/sql

如下示例使用事务来更新用户:

*sql.DB.BeginTxcontext.Context*sql.TxOptionsIsolation
txExecContextdb.ExecContext
tx.Rollback()
tx.Commit()
txPrepare

处理 NULL

UserNamesql.NullStringstringNULL
name
nameNULL'''n1'
string''stringNULL

这个时候,我们有两种方法解决此问题:

sql.NullString
nilnilNULLUserBirthday*time.Time
sql.NullString
StringValidNULL
NullString
valuevalue for MySQL
{String:n1 Valid:true}'n1'
{String: Valid:true}''
{String: Valid:false}NULL
sql.NullStringsql.Scannerdriver.Valuer
*sql.Row.Scan*sql.DB.Exec
*sql.DB.ExecNamedatabase/sqlsql.NullStringValue()
*sql.Row.ScannameUserNamedatabase/sqlsql.NullStringScan()Name
stringdatabase/sqlsql.NullBoolsql.NullFloat64
NULL

自定义字段类型

salary{"month":100000,"year":10000000}
salary
string",
salarystruct
*sql.Row.Scan*sql.DB.Exec
sql.NullStringSalarysql.Scannerdriver.Valuer
Salary

未知列

*sql.DB.Query
*sql.Rows.Columns

示例代码如下:

*sql.Rows.ColumnTypescolumn
sql.RawBytes[]byte

总结

database/sql
sql.Open()
*sql.DB.Exec*sql.DB.ExecContextExecExecContext*sql.DB.Ping*sql.DB.Query*sql.DB.QueryRow*sql.DB.PrepareXxxContext
Error 1064 (42000): You have an error in your SQL syntax;
*sql.DB.BeginTxCommitRollback*sql.TxOptions
NULLdatabase/sqlsql.NullStringsql.Scannerdriver.Valuer
*sql.Rows.Columnssql.RawBytes