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
| value | value 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