GORM 已经优化了许多东西来提高性能,其默认性能对大多数应用来说都够用了。但这里还是有一些关于如何为您的应用改进性能的方法。
禁用默认事务
对于写操作(创建、更新、删除),为了确保数据的完整性,GORM 会将它们封装在事务内运行。但这会降低性能,如果没有这方面的要求,您可以在初始化时禁用它,这将获得大约 30%+ 性能提升。
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
SkipDefaultTransaction: true,
})
缓存预编译语句
执行任何 SQL 时都创建并缓存预编译语句,可以提高后续的调用速度
// 全局模式
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
PrepareStmt: true,
})
// 会话模式
tx := db.Session(&Session{PrepareStmt: true})
tx.First(&user, 1)
tx.Find(&users)
tx.Model(&user).Update("Age", 18)
选择字段
默认情况下,GORM 在查询时会选择所有的字段,您可以使用 Select 来指定您想要的字段
db.Select("Name", "Age").Find(&Users{})
或者这样指定需要的字段
type User struct {
ID uint
Name string
Age int
Gender string
// 假设后面还有几百个字段...
}
type APIUser struct {
ID uint
Name string
}
// 查询时会自动选择 `id`、`name` 字段
db.Model(&User{}).Limit(10).Find(&APIUser{})
// SELECT `id`, `name` FROM `users` LIMIT 10
迭代、FindInBatches
用迭代或 in batches 查询并处理记录
Iteration
rows, err := db.Model(&User{}).Where("name = ?", "jinzhu").Rows()
defer rows.Close()
for rows.Next() {
var user User
// ScanRows is a method of `gorm.DB`, it can be used to scan a row into a struct
db.ScanRows(rows, &user)
// do something
}
FindInBatches
// batch size 100
result := db.Where("processed = ?", false).FindInBatches(&results, 100, func(tx *gorm.DB, batch int) error {
for _, result := range results {
// batch processing found records
}
tx.Save(&results)
tx.RowsAffected // number of records in this batch
batch // Batch 1, 2, 3
// returns error will stop future batches
return nil
})
result.Error // returned error
result.RowsAffected // processed records count in all batches
interpolateParams=true
开启 interpolateparams 以减少 roundtrip
[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...¶mN=valueN&interpolateParams=true]
If interpolateParams is true, placeholders (?) in calls to db.Query() and db.Exec() are interpolated into a single query string with given parameters. This reduces the number of roundtrips, since the driver has to prepare a statement, execute it with given parameters and close the statement again with interpolateParams=false.
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
)
func main() {
//主库用读写账户 建立mysql链接时使用参数interpolateParams=true
db_master, err := sql.Open("mysql", "master:@tcp(127.0.0.1:3306)/db_test?charset=utf8&interpolateParams=true")
if err != nil {
panic(err.Error()) //只是举例,真实使用中需要对错误进行处理和返回
}
defer db_master.Close()
//从库用只读账户 建立mysql链接时使用参数interpolateParams=true
db_slave, err := sql.Open("mysql", "slave:@tcp(127.0.0.1:3306)/db_test?charset=utf8&interpolateParams=true")
if err != nil {
panic(err.Error())
}
defer db_slave.Close()
rows, err := db_slave.Query("SELECT * FROM tbl_user WHERE user_id = ?", 1)
if err != nil {
panic(err.Error())
}
}
但注意interpolateParams=true不可以与以下多字节编码共同使用(multibyte encodings BIG5, CP932, GB2312, GBK or SJIS),因为他们会引起sql注入脆弱性。
读写分离
通过读写分离提高数据吞吐量,查看 Database Resolver 获取详情
关闭ping
在完成初始化后,GORM 会自动 ping 数据库以检查数据库的可用性,若要禁用该特性,可将其设置为 true.如果有其他措施保证数据库是可用的,该项也可用关闭
db, err := gorm.Open(sqlite.Open("gorm.db"), &gorm.Config{
DisableAutomaticPing: true,
})
参考
1、https://gorm.io/zh_CN/docs/performance.html
2、https://github.com/go-sql-driver/mysql#interpolateparams
3、https://blog.csdn.net/weixin_29724477/article/details/112098670