这个例子将CRUD操作分为两个部分,即 "写 "和 "读"。写 "部分包括INSERT,UPDATE 和DELETE 查询。读 "部分涵盖了SELECT 查询。我们也在使用context.WithTimeout 来终止长期运行的查询。请注意SELECT 查询,因为它们依赖于一个叫做MAX_EXECUTION_TIME 的东西。
插入/更新/删除
预备(安全选项)
如果你不能100%确定查询参数是没有SQL注入的,那么首选这个。
这是一个缓慢的操作,因为它是网络往返次数的三倍 (Prepare,Execute 和Close)。该查询使用? 参数占位符。
下面两个例子的工作方式完全相同,所以我建议使用第二个例子,因为它更干净。另外,无论查询参数是动态的还是静态的,它仍然是网络往返次数的三倍。
func Create(args ...interface{}) error {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()
stmt, err := db.PrepareContext(ctx, `
INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
(?, ?, ?, ?, ?, ?, UTC_TIMESTAMP())
`)
if err != nil {
return err
}
defer stmt.Close()
res, err := stmt.ExecContext(ctx, args...)
if err != nil {
return err
}
tot, err := res.RowsAffected()
if err != nil {
return err
}
if tot != 1 {
return errors.New("no rows were affected")
}
return nil
}
func Create(args ...interface{}) error {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()
q := `
INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
(?, ?, ?, ?, ?, ?, UTC_TIMESTAMP())
`
res, err := db.ExecContext(ctx, q, args...)
if err != nil {
return err
}
tot, err := res.RowsAffected()
if err != nil {
return err
}
if tot != 1 {
return errors.New("no rows were affected")
}
return nil
}
2020-03-13T13:14:36.035537Z 9 Prepare INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
(?, ?, ?, ?, ?, ?, UTC_TIMESTAMP())
2020-03-13T13:14:36.038073Z 9 Execute INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
('6a980454-0727-4260-a757-ce619e79af83', 'League 1', 'Address', 2, 0, '2001-01-01', UTC_TIMESTAMP())
2020-03-13T13:14:36.052590Z 9 Close stmt
格式化(不安全选项)
如果你100%确定查询参数不存在SQL注入问题,那么请选择这个选项。
这是一个快速的操作,因为它只发出一个网络往返(Query)。使用适当的参数格式化器,用fmt.Sprintf 函数来格式化查询。
func Create(args ...interface{}) error {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()
q := fmt.Sprintf(`
INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
('%s', '%s', '%s', %d, %t, '%s', UTC_TIMESTAMP())
`,
args...,
)
res, err := db.ExecContext(ctx, q)
if err != nil {
return err
}
tot, err := res.RowsAffected()
if err != nil {
return err
}
if tot != 1 {
return errors.New("no rows were affected")
}
return nil
}
2020-03-13T13:30:26.812083Z 15 Query INSERT INTO leagues
(uuid, name, address, int_rank, is_active, founded_at, created_at)
VALUES
('2250b0d9-365a-4655-a116-1b092f6d6f20', 'League 1', 'Address', 2, false, '2001-01-01', UTC_TIMESTAMP())
注意
尽管我们使用context.WithTimeout 来取消长期运行的查询,但由于其协议性质,MySQL实际上不会终止基础查询。取消只发生在Go级别。然而,你可以通过使用MySQL的MAX_EXECUTION_TIME查询提示来解决SELECT 查询的这个问题,以便告诉MySQL服务器,如果它运行的时间超过了给定的时间限制,就终止执行。如果是这样,它将产生Error 3024: Query execution was interrupted, maximum statement execution time exceeded 错误。
选择一
预备(安全选项)
如果你不能100%确定查询参数是没有SQL注入的。
这是一个缓慢的操作,因为它是网络往返次数的三倍(Prepare,Execute 和Close )。该查询使用? 参数占位符。
无论查询参数是动态的还是静态的,它仍然会使网络往返次数增加三倍。
type League struct {
ID sql.NullInt64
UUID sql.NullString
Name sql.NullString
Address sql.NullString
IntRank sql.NullInt64
IsActive sql.NullBool
FoundedAt mysql.NullTime
CreatedAt mysql.NullTime
DeletedAt mysql.NullTime
}
func Read(uuid string) (League, bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()
row := db.QueryRowContext(ctx, `
SELECT
/*+ MAX_EXECUTION_TIME(3000) */
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = ?
LIMIT 1`,
uuid,
)
var model League
err := row.Scan(
&model.ID,
&model.UUID,
&model.Name,
&model.IntRank,
&model.Address,
&model.IsActive,
&model.FoundedAt,
&model.CreatedAt,
&model.DeletedAt,
)
switch {
case err == sql.ErrNoRows:
return model, false, nil // 404
case err != nil:
return model, false, err // 500
default:
return model, true, nil // 200
}
}
2020-03-13T16:00:11.624465Z 16 Prepare SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = ?
LIMIT 1
2020-03-13T16:00:11.643215Z 16 Execute SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = '2250b0d9-365a-4655-a116-1b092f6d6f20'
LIMIT 1
2020-03-13T16:00:11.674942Z 16 Close stmt
格式化(不安全选项)
如果你100%确定查询参数是没有SQL注入的。
这是一个快速的操作,因为它只发出一个网络往返(Query)。使用适当的参数格式化器,用fmt.Sprintf 函数对查询进行格式化。
func Read(uuid string) (League, bool, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()
q := fmt.Sprintf(`
SELECT
/*+ MAX_EXECUTION_TIME(3000) */
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = '%s'
LIMIT 1
`,
uuid,
)
row := db.QueryRowContext(ctx, q)
var model League
err := row.Scan(
&model.ID,
&model.UUID,
&model.Name,
&model.IntRank,
&model.Address,
&model.IsActive,
&model.FoundedAt,
&model.CreatedAt,
&model.DeletedAt,
)
switch {
case err == sql.ErrNoRows:
return model, false, nil // 404
case err != nil:
return model, false, err // 500
default:
return model, true, nil // 200
}
}
2020-03-13T16:06:49.526538Z 18 Query SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
WHERE
uuid = '2250b0d9-365a-4655-a116-1b092f6d6f20'
LIMIT 1
选择 "大量"(SELECT MANY)
预备(安全选项)
如果你不能100%确定查询参数是没有SQL注入的。
这是一个缓慢的操作,因为它是网络往返次数的三倍(Prepare,Execute 和Close)。该查询使用? 参数占位符。
无论查询参数是动态的还是静态的,它仍然会使网络往返次数增加三倍。
type League struct {
ID sql.NullInt64
UUID sql.NullString
Name sql.NullString
Address sql.NullString
IntRank sql.NullInt64
IsActive sql.NullBool
FoundedAt mysql.NullTime
CreatedAt mysql.NullTime
DeletedAt mysql.NullTime
}
func Read(limit, offset int) ([]League, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()
rows, err := db.QueryContext(ctx, `
SELECT
/*+ MAX_EXECUTION_TIME(3000) */
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT ?
OFFSET ?`,
limit,
offset,
)
var models []League
if err != nil {
return models, err // 500
}
defer rows.Close()
for rows.Next() {
var model League
err := rows.Scan(
&model.ID,
&model.UUID,
&model.Name,
&model.IntRank,
&model.Address,
&model.IsActive,
&model.FoundedAt,
&model.CreatedAt,
&model.DeletedAt,
)
if err != nil {
return models, err // 500
}
models = append(models, model)
}
if err = rows.Err(); err != nil {
return models, err // 500
}
return models, nil
}
2020-03-13T16:35:51.216389Z 22 Prepare SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT ?
OFFSET ?
2020-03-13T16:35:51.223332Z 22 Execute SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT 10
OFFSET 0
2020-03-13T16:35:51.245423Z 22 Close stmt
格式化(不安全选项)
如果你100%确定查询参数是没有SQL注入的。
这是一个快速的操作,因为它只发出一个网络往返(Query)。使用适当的参数格式化器,用fmt.Sprintf 函数对查询进行格式化。
func Read(limit, offset int) ([]League, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3 * time.Second)
defer cancel()
q := fmt.Sprintf(`
SELECT
/*+ MAX_EXECUTION_TIME(3000) */
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT %d
OFFSET %d
`,
limit,
offset,
)
rows, err := db.QueryContext(ctx, q)
var models []League
if err != nil {
return models, err // 500
}
defer rows.Close()
for rows.Next() {
var model League
err := rows.Scan(
&model.ID,
&model.UUID,
&model.Name,
&model.IntRank,
&model.Address,
&model.IsActive,
&model.FoundedAt,
&model.CreatedAt,
&model.DeletedAt,
)
if err != nil {
return models, err // 500
}
models = append(models, model)
}
if err = rows.Err(); err != nil {
return models, err // 500
}
return models, nil
}
2020-03-13T16:39:42.933817Z 23 Query SELECT
id, uuid, name, int_rank, address, is_active, founded_at, created_at, deleted_at
FROM leagues
LIMIT 10
OFFSET 0