golang-非orm数据库的操作与对比 database/sql、sqlx 和 sqlc
简单介绍
database/sql
database/sql 是一个标准库软件包,负责与数据库(主要是 SQL 关系数据库)的连接和交互。
它为类 SQL 交互提供泛型接口、类型和方法。database/sql 在创建时将简单易用纳入考量,配置为支持与类 SQL 数据库交互所需的最基本必要功能。
为了与数据库管理系统交互,数据库软件包需要适当的驱动程序。 目前,database/sql 支持超过 50 种数据库驱动程序,涵盖 SQLite、MySQL/MariaDB、PostgreSQL、Oracle 和 MS SQL Server 等最流行的 DBMS,能够增强适配性,增强移植性。
此软件包还支持基本的 CRUD 操作、数据库事务、命名形参、返回多个结果集、可取消查询、SQL 类型支持、连接池管理、形参化查询和预备语句等功能。
尽管它支持许多基本的现代数据库功能,例如事务和预备语句,但它也有一些局限性。 例如,它存在类型限制,无法将大的 uint64 值作为形参传递给语句。
它的主要功能和特性可以总结为以下几点:
- 统一的编程接口:database/sql包通过提供一组统一的API,如Prepare(), Exec(), Query()等,使得开发人员能够以相同的方式操作不同的数据库。这大大提高了代码的可移植性和灵活性。
- 驱动支持:database/sql包本身并不直接与数据库通信,而是依赖于第三方数据库驱动程序。这些驱动程序需要实现database/sql/driver包中定义的Driver接口,并在程序初始化阶段通过sql.Register()方法注册到database/sql中。常见的关系型数据库如MySQL、PostgreSQL、Oracle、Gbase8s等都有对应的Go语言驱动程序。比如oracle的驱动mattn/go-oci8: Oracle driver for Go using database/sql (github.com)
- 连接池管理:database/sql维护了一个数据库连接池,用于管理数据库连接。当通过sql.Open()打开一个数据库连接时,database/sql会在合适的时机调用注册的驱动来创建一个具体的连接,并将其添加到连接池中。连接池会负责连接的复用、管理和维护工作,并且这是并发安全的。
- 事务支持:database/sql包还支持事务处理,可以通过Tx类型的方法如Begin(), Commit(), Rollback()等来进行事务的管理。
- 安全性:为了防止SQL注入攻击,database/sql包推荐使用预编译语句和参数化查询。这样可以确保所有的SQL语句在执行前都会被预先分析和编译,从而避免了潜在的安全问题。
sqlx
创建 sqlx 是为了扩展标准库数据库软件包的功能。 由于它依赖 database/sql 软件包,后者提供的所有功能也可用,包括对同一组数据库技术和驱动程序的支持。
除了这些核心功能之外,sqlx 还具有以下优点:
- 带有命名形参的预备语句 – 这使您能够使用结构字段的名称和映射键绑定预备语句或查询中的变量。
- 结构扫描 – 这允许您将查询结果直接扫描到单行的结构中,不必像 database/sql 那样单独扫描每个字段或列。 它还支持扫描到嵌入式结构。
- Select 和 Get – 这些是用于处理预期将多个记录或单个记录分别返回到结构的切片或单个结构的查询的便捷方法。 不需要循环结果集!
- 对 IN 查询的支持 – 这允许您将值的切片作为单个形参绑定到 IN 查询。 与将切片作为单个值处理的 database/sql 相比,切片在预期位置上以相同数量的 bindvars 展开。
命名查询 – 这会将结构字段的名称绑定到列名称,避免在向 bindvars 赋值时对列名称的位置引用。 - 无错误结果集:结果集不返回错误,允许对返回结果进行链式操作,例如将结果直接扫描到结构中。 如以下代码段所示:
var p Place
err := db.QueryRowx("SELECT city, telcode FROM place LIMIT 1").StructScan(&p)
这些只是 sqlx 软件包众多功能中的几个例子,这些功能确保了比 database/sql 更好的工作体验。
sqlc
sqlc 是一个捆绑为可执行二进制文件的 SQL 编译器,可以为原始 SQL 架构和查询生成类型安全代码。 因此,除了实际的 SQL 语句之外,您不必编写任何样板代码。
根据文档,它可以为 PostgreSQL、MySQL/MariaDB 和 SQLite 生成代码。 然而,生成的代码也适用于标准库的 SQL 软件包;因此,它可以使用所有支持的驱动程序,但不一定使用支持的数据库。 除了支持的数据库和驱动程序之外,以下是它的一些其他功能:
- 查询注解 – 这些注解允许您为每个查询定义函数的名称以及预期的查询结果类型。 它们在代码生成期间用于确定函数的名称和签名。
- JSON 标记 – sqlc 支持为将被编组并作为 JSON 发送给客户端的结构或类型生成 JSON 标记。
- 架构修改 – sqlc 支持读取各种格式的迁移文件以修改架构并生成代码来反映这些更改。
- 结构命名 – sqlc 提供用于从表名称生成结构名称的命名方案的选项。
如果您擅长 SQL 语句,并且不喜欢使用太多代码执行数据库操作和处理数据,那么这个软件包绝对适合您。
database/sql
首先需要连接与defer关闭数据库
package mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql" // 导入MySQL驱动"log"
)func main() {// 打开数据库连接db, err := sql.Open("mysql", "username:password@tcp(localhost:3306)/dbname")if err != nil {log.Fatal(err) //or panic}defer db.Close()// 执行你想要的操作
}
后续可单独封装出db对象
需要关闭数据库!
Go的database/sql包在设置了连接池后,仍然需要关闭连接。尽管连接池的设计目的是为了复用数据库连接,提高性能并避免频繁地建立和断开连接,但在使用完数据库连接后,应该显式地关闭它。这是因为关闭连接并不是真正意义上的断开与数据库的TCP连接,而是将连接返回到连接池中,以便其他请求可以复用。如果不关闭连接,连接将会一直被占用,可能导致连接池中的连接被耗尽
测连接是否能ping通
err = db.Ping()
if err != nil {panic(err)
}
连接池设置
在正常情况下,database/sql的连接池会维护一定数量的活跃和空闲连接。
SetMaxIdleConns()
用于设置连接池中空闲连接的最大数量。
SetMaxOpenConns()
用于设置到数据库的同时最大打开连接数。
SetConnMaxLifetime()
可以设置连接的最大生命周期,超过这个时间,连接将被关闭并从池中移除。
插入、删除、更新—— Exec
以下代码段演示了如何使用带有 MySQL 驱动程序的 database/sql 软件包插入记录:
func addStudent(s Student) (int64, error){query := "insert into students (fname, lname, date_of_birth, email, gender, address) values (?, ?, ?, ?, ?, ?);"result, err := db.Exec(query, s.Fname,s.Lname, s.DateOfBirth, s.Email, s.Gender, s.Address)if err != nil {return 0, fmt.Errorf("addStudent Error: %v", err)}id, err := result.LastInsertId()if err != nil {return 0, fmt.Errorf("addStudent Error: %v", err)}return id, nil
}
正如您所看到的,插入操作与编写直接 SQL 语句非常相似。 您还将需要分别输入每个字段及其关联值。 然而,随着时间推移,在大型结构或复杂类型中维护代码会变得很麻烦,增加引入错误的机会,而这些错误可能只能在运行时被捕获。
查询 Query与QueryRow
package mainimport ("database/sql""fmt"_ "github.com/go-sql-driver/mysql" // 导入MySQL驱动"log"
)func main() {// 打开数据库连接db, err := sql.Open("mysql", "username:password@tcp(localhost:3306)/dbname")if err != nil {log.Fatal(err)}defer db.Close()// 执行查询rows, err := db.Query("SELECT id, name FROM users WHERE active = ? and deleted= ?", 1,0)if err != nil {log.Fatal(err)}defer rows.Close()// 遍历查询结果for rows.Next() {var this_is_id intvar this_is_name string//Scan会按照顺序将select中的值赋值给括号中的变量if err := rows.Scan(&this_is_id, &this_is_name); err != nil {log.Fatal(err)}//打印出来 fmt.Printf("ID: %d, Name: %s\n", this_is_id, this_is_name)}// 检查遍历是否出现错误if err := rows.Err(); err != nil {log.Fatal(err)}
}
在这个示例中,我们使用了Go的database/sql标准库和MySQL驱动。首先,我们使用sql.Open()建立了与数据库的连接。然后,我们使用db.Query()执行了一个查询,查询活跃用户的id和name。最后,我们通过rows.Next()遍历结果集,并通过rows.Scan()将结果存入变量。如果在处理过程中发生错误,我们记录日志并退出程序。
或者还可以将其保存在数组、map或者结构体数组等中(通过append或者直接赋值等方法) 比如
func fetchStudents() ([]Student, error) {var students []Studentrows, err := db.Query("SELECT * FROM students")if err != nil {return nil, fmt.Errorf("fetchStudents %v", err)}defer rows.Close()for rows.Next() {var s Studentif err := rows.Scan(&s.ID, &s.Fname, &s.Lname, &s.DateOfBirth, &s.Email, &s.Address, &s.Gender ); err != nil {return nil, fmt.Errorf("fetchStudents %v", err)}students = append(students, s)}if err := rows.Err(); err != nil {return nil, fmt.Errorf("fetchStudents %v", err)}return students, nil
}
在上面的代码段中,获取记录后对其循环,使用 row.Scan() 将每个记录的每个字段单独扫描到一个结构中,然后将结构附加到一个切片。 这里需要小心,因为提供的结构中的字段数必须等于返回的记录中的字段数。
QueryRow方法则是只扫描一行
var primary string
err := c.db.QueryRow("select ha_primary from sysha_type").Scan(&primary)
您还应该注意,很难使用 IN 子句处理查询,因为它将 IN bindvar 值视为单个值,而不是多个值。
in的特殊处理
ids := []int{5,6,7}sqlstr := `select * from student where id in (?) `rows, err = db.Query(sqlstr, ids )
会报错 sql: converting argument $1 type: unsupported type []int, a slice of int
于是猜测是因为要问号要匹配的的缘故
于是引入一个函数去处理
func placeholders(n int) string {var b strings.Builderfor i := 0; i < n - 1; i++ {b.WriteString("?,")}if n > 0 {b.WriteString("?")}return b.String()
}func (c *GetMovieInfoController) Get() {ids := []int{5,6,7}db := models.ConnectDb();defer db.Close()query := fmt.Sprintf("select id,movie_name ,movie_director from movie_info where id in (%s)", placeholders(len(ids)))rows, err := db.Query(query,ids)
还是不行 所以是不支持直接数组
解决方法1
ids := []int{}{5,6,7}
query := fmt.Sprintf("select id,movie_name,movie_director from movie_info where id in (%s)", placeholders(len(ids)))
rows, err := db.Query(query,ids...) //加... 这里看golang基础可变参数那里
如果是Exec (包括sqlx的),由于函数定义就是
func (db *DB) Exec(query string, args ...interface{})
所以
ids := []int{}{5,6,7}
要改为ids := []intereface{}{5,6,7}
或者
// 需要查询的ID列表ids := []int{1, 2, 3}// 准备查询语句query := `SELECT * FROM your_table WHERE your_column IN (?)`// 准备一个args切片来构造查询参数args := []interface{}{ids}
解决方法2
func formatids(ids []int) ([]interface{},string) {inIds := ""params:=make([]interface{},0)for i:=0;i<len(ids);i++{if i==0{inIds+="?"}else{inIds+=",?"}params=append(params , ids[i])}return params,inIds
}func (c *GetMovieInfoController) Get() {ids := []int{8,9,10}db := models.ConnectDb();defer db.Close()sql := "select id,movie_name,movie_director from movie_info where id in (%s)"params,inIds := formatids(ids)sql = fmt.Sprintf(sql ,inIds )fmt.Fprint(c.Ctx.ResponseWriter,sql)rows,err := db.Query(sql , params...)
解决方法3
int好像没这个功能
func (c *GetMovieInfoController) Get() { ids := []string{"8", "9", "10"}db := models.ConnectDb();defer db.Close()idStr := strings.Join(ids, "','")fmt.Println(idStr)sqlText := "select id,movie_name,movie_director from movie_info where id in ('%s')"sqlText = fmt.Sprintf(sqlText, idStr)rows,err := db.Query(sqlText)
}
解决方法4 py.Array
将切片参数转换为 pq.Array 类型:
ids := []int{1, 2, 3}
query := `SELECT * FROM your_table WHERE id = ANY($1)`
// 将切片转换为 pq.Array 类型
pqIds := pq.Array(ids)// 使用 stdlib's database/sql
result, err := db.Query(query, pqIds)// 或者使用 sqlx
var results []YourStruct
err := db.Select(&results, query, pqIds)
解决方法5
如果你使用的数据库和语言不支持这种操作,你可能需要手动构造SQL查询,将切片元素拼接成逗号分隔的字符串,并确保处理好 SQL 注入的风险。
ids := []int{1, 2, 3}
idStrings := []string{}
for _, id := range ids {idStrings = append(idStrings, strconv.Itoa(id))
}
query := `SELECT * FROM your_table WHERE id IN (` + strings.Join(idStrings, ",") + `)`// 使用 database/sql 或 sqlx 执行查询
注意:直接拼接字符串可能会有 SQL 注入的风险,如果你的参数来源不可信,请确保适当地清理或预处理参数。
?与$
在Go语言中,使用database/sql包与数据库交互时,可以使用问号(?)或者占位符($1, $2 等)来预处理SQL语句并防止SQL注入。
问号(?)通常用于占位符,它是一种参数化查询的形式,可以在执行时替换为实际的值。使用问号的优点是代码简洁,缺点是不能重复使用一个占位符。
占位符($1, $2 等)是PostgreSQL的语法,它允许你在SQL语句中多次使用相同的占位符,并在执行时分别替换为不同的值。
以下是使用问号和PostgreSQL占位符的例子:
使用问号:
import ("database/sql"_ "github.com/lib/pq"
)func main() {db, err := sql.Open("postgres", "your_connection_string")if err != nil {panic(err)}defer db.Close()// 使用问号作为占位符stmt, err := db.Prepare("SELECT * FROM users WHERE username = $1")if err != nil {panic(err)}defer stmt.Close()// 执行查询rows, err := stmt.Query("alice")if err != nil {panic(err)}defer rows.Close()// ... 处理rows
}
使用PostgreSQL的占位符:
import ("database/sql"_ "github.com/lib/pq"
)func main() {db, err := sql.Open("postgres", "your_connection_string")if err != nil {panic(err)}defer db.Close()// 使用PostgreSQL的占位符stmt, err := db.Prepare("SELECT * FROM users WHERE username = $1 AND password = $2")if err != nil {panic(err)}defer stmt.Close()// 执行查询rows, err := stmt.Query("alice", "password123")if err != nil {panic(err)}defer rows.Close()// ... 处理rows
}
在Go中,通常使用$1, 2 这样的占位符来防止 S Q L 注入,尤其是在处理动态查询时。而问号 ( ? ) 通常用于简单的参数化查询,尤其是在使用 M y S Q L 这类数据库时。如果你使用的数据库不支持 2 这样的占位符来防止SQL注入,尤其是在处理动态查询时。而问号(?)通常用于简单的参数化查询,尤其是在使用MySQL这类数据库时。如果你使用的数据库不支持 2这样的占位符来防止SQL注入,尤其是在处理动态查询时。而问号(?)通常用于简单的参数化查询,尤其是在使用MySQL这类数据库时。如果你使用的数据库不支持占位符,你只能使用问号。
sql注入与预处理 Prepare
为什么占位符能够避免sql注入?
占位符 ? 实际生成的结果,变量会被加引号处理,不会包含有害语句,故可以保证sql注入问题 也就是说 你的sql语句和参数是分开的
select id,namem from mtable1 where name=""kate";drop table table2;"
/sql预处理也可以也可以防止注入问题,
预处理 只是会缓存你执行sql的一些通用的步骤 这样减少重复计算消耗
1、预处理可以提高性能,一次编译多次执行
2、语句与查询条件是分开的,可以保证sql注入问题
什么是预处理?
普通SQL语句的执行过程
- 客户端对SQL语句进行占位符的替换得到了完整的SQL语句
- 客户端发送完整SQL语句到MySOL服务端
- MySQL服务端执行完整的SQL语句并将结果返回终端
预处理的执行过程
- 先把SQL语句拆分成两部分,SQL语句部分和参数部分
- 先把SOL语句部分发送给MySQL服务端进行SQL预处理
- 然后参数部分发送给MySQL服务端,MySOL对SQL语句进行拼接
- MySQL服务端执行完整的SQL语句返回结果
为什么要预处理
- 优化MYSQL服务器重复执行SQL的方法。可以执行服务器的性能,提前让服务器编译,一次编译多次执行,节省后续重复编译的成本
- 避免SQL注入(普通占位符也可以避免)
我们使用 PREPARE 准备这个SQL语句,并通过 EXECUTE 来执行它。最后,记得使用 DEALLOCATE 释放预处理语句占用的资源。
总结来说,PREPARE 和 EXECUTE 提供了一种安全且高效的方式来动态执行SQL语句,特别是在需要多次执行相似SQL但参数不同的场景中。
事务
略
sqlx
sqlx 主要是 Go database/sql 软件包的扩展
在 GitHub 上,sqlx 获得了超过 12,800 颗星,拥有大量关注者和非常活跃的问题列表。
如果您有 database/sql 背景,您将顺利过渡,因为查询在语法上相似且兼容。 从以下代码段中,您会注意到与先前的 database/sql 代码的很多相似之处,尤其是 insert 语句:
func addStudent(s Student) (int64, error){query := "insert into students (fname, lname, date_of_birth, email, gender, address) values (?, ?, ?, ?, ?, ?);"result := db.MustExec(query, s.Fname,s.Lname, s.DateOfBirth, s.Email, s.Gender, s.Address)id, err := result.LastInsertId()if err != nil {return 0, fmt.Errorf("addStudent Error: %v", err)}return id, nil
}
由于使用 db.MustExec()
方法减少了错误检查的需要,代码行数比 database/sql
少。
以下代码展示了如何使用 sqlx 便捷方法检索多个记录:
func fetchStudents() ([]Student, error) {var students []Studenterr := db.Select(&students,"SELECT * FROM students LIMIT 10")if err != nil {return nil, fmt.Errorf("fetchStudents %v", err)}return students, nil
}
正如您所看到的,相对于使用 db.Query()
编写与 database/sql
兼容的语法并循环遍历结果,sqlx
提供了更简单、更清晰的代码,帮助您使用 db.Select()
实现相同的目标。
它甚至提供了一种更好的方式来处理带有 IN 子句的查询,如以上“功能”部分所述。
sqlc
前述 sqlc 生成类型安全代码的能力是易用性的另一个好处,因为它减少了为数据库操作编写的 Go 代码量,节省您的时间和精力。
sqlc 拥有一个超过 6,800 颗星的活跃且不断壮大的社区,还拥有强力的参与度和社区支持。
文档提供了从安装到代码生成的分步演示,如果您想要更具互动性的方式,可以观看简明的视频教程。 sqlc 非常容易上手,如下所示。
安装二进制文件后,在 sqlc.yaml 中编写配置文件,它应该类似于下面的代码段:
version: 1
packages:- path: "./"name: "main"engine: "mysql"schema: "schema.sql"queries: "query.sql"
现在,您需要做的就是在 2 个文件中编写普通的 SQL,如下图所示,然后在工作目录中运行命令 sqlc generate:
以下列表解释了上图的元素:
- 红框显示了 3 个新生成的文件:models.go 对应于结构、query.sql.go 和 db.go 对应于其他数据库相关代码。
- 黄框高亮显示了包含架构定义的 schema.sql 文件。
- 蓝框高亮显示了 query.sql 文件,其中包含应用程序数据库操作的所有 SQL 语句,以及一些用于生成函数的元数据。
- 绿框高亮显示了 sqlc 在终端的工作目录中生成的运行。
现在,您只需使用 database/sql 软件包提供数据库连接,然后调用所需的方法而不必修改生成的代码,如以下代码段所示:
func main() {// Create a new database connectionconn, err := sql.Open("mysql", "theuser:thepass@tcp(localhost:3306)/thedb?parseTime=true")if err != nil {log.Fatal(err)}fmt.Println("Connected!")db := New(conn)// Initialize record to be insertednewSt := addStudentParams{Fname: "Leon",Lname: "Ashling",DateOfBirth: time.Date(1994, time.August, 14, 23, 51, 42, 0, time.UTC),Email: "lashling5@senate.gov",Gender: "Male",Address: "39 Kipling Pass",}// Insert the recordsID, err := db.addStudent(context.Background(), newSt)if err != nil {log.Fatal(err)}fmt.Printf("addStudent id: %v n", sID)// Fetch the recordsstudents, err := db.fetchStudents(context.Background())if err != nil {log.Println(err)}fmt.Printf("fetchStudents count: %v n", len(students))
}
如果您擅长编写 SQL 语句,那么这个软件包生成的类型安全代码是更好的选择,特别是在必须与许多不同类型的架构交互的情况下。
GORM
GORM 有非常全面而简单的入门文档和指南。 不过,GORM 更多地采用基于代码的方法与数据库交互,一开始有一个陡峭的学习曲线。
使用 GORM 语法,您可能需要花费大量时间来最初构造类似于原始 SQL 查询的代码。 然而,当您熟悉了语法,您将拥有一个干净的代码库,很少与原始 SQL 交互。
GORM 是 GitHub 上第二大的 Go 数据库软件包(仅次于 database/sql),拥有超过 30,400 颗星,不会缺少社区支持。
以下示例演示了如何使用 GORM 实现与之前相同的 insert 语句和多个记录查询:
func main() {// Open a database connectiondb, err := gorm.Open(mysql.Open("theuser:thepass@tcp(127.0.0.1:3306)/thedb?charset=utf8mb4&parseTime=True&loc=Local"))if err != nil {log.Fatal(err)}fmt.Println("Connected!")// Initialize record to be inserteds := Student{Fname: "Leon",Lname: "Ashling",DateOfBirth: time.Date(1994, time.August, 14, 23, 51, 42, 0, time.UTC),Email: "lashling5@senate.gov",Address: "39 Kipling Pass",Gender: "Male",}// Add student record and return the ID into the ID fielddb.Create(&s)fmt.Printf("addStudent id: %v n", s.ID)// Select multiple recordsvar students []Studentdb.Limit(10).Find(&students)fmt.Printf("fetchStudents count: %v n", len(students))
}
如以上代码段所示,代码相对简单,只有很少几行。 通过了陡峭的初始学习曲线后,GORM 实际上在编写查询方面比其他 3 个选项更有效。
更多请看另一篇文章
性能和速度
数据库操作的性能和速度对于以数据为中心的应用程序的整体性能至关重要。 最合适的数据库软件包高度依赖于性能,特别是在开发低延迟应用程序时。
本部分比较所有 4 个数据库包的性能。 为进行此基准化分析,已经建立了一个包含 15,000 个学生记录的 MySQL/MariaDB 数据库。 由于这些软件包的大多数用例都是获取记录的查询,基准化分析捕获了所有 4 个软件包获取 1、10、100、1000、10,000 和 15,000 个记录并将其扫描到结构中的性能:
================================== BENCHMARKING 1 RECORDS ======================================
goos: linux
goarch: amd64
pkg: github.com/rexfordnyrk/go-db-comparison/benchmarks
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
Benchmark/Database/sql_limit:1_-8 9054 124134 ns/op
Benchmark/Sqlx_limit:1_-8 8914 138792 ns/op
Benchmark/Sqlc_limit:1_-8 7954 147056 ns/op
Benchmark/GORM_limit:1_-8 13388 89251 ns/op
=================================================================================================
================================== BENCHMARKING 10 RECORDS ======================================
Benchmark/Database/sql_limit:10_-8 7576 157780 ns/op
Benchmark/Sqlx_limit:10_-8 4384 260402 ns/op
Benchmark/Sqlc_limit:10_-8 4183 256384 ns/op
Benchmark/GORM_limit:10_-8 9466 136556 ns/op
=================================================================================================
================================== BENCHMARKING 100 RECORDS ======================================
Benchmark/Database/sql_limit:100_-8 2521 427603 ns/op
Benchmark/Sqlx_limit:100_-8 2139 497755 ns/op
Benchmark/Sqlc_limit:100_-8 2838 456938 ns/op
Benchmark/GORM_limit:100_-8 1896 563539 ns/op
=================================================================================================
================================== BENCHMARKING 1000 RECORDS ======================================
Benchmark/Database/sql_limit:1000_-8 516 2201303 ns/op
Benchmark/Sqlx_limit:1000_-8 445 2786983 ns/op
Benchmark/Sqlc_limit:1000_-8 535 2313674 ns/op
Benchmark/GORM_limit:1000_-8 315 4186201 ns/op
=================================================================================================
================================== BENCHMARKING 10000 RECORDS ======================================
Benchmark/Database/sql_limit:10000_-8 51 21690323 ns/op
Benchmark/Sqlx_limit:10000_-8 38 28458473 ns/op
Benchmark/Sqlc_limit:10000_-8 55 21558300 ns/op
Benchmark/GORM_limit:10000_-8 28 40463924 ns/op
=================================================================================================
================================== BENCHMARKING 15000 RECORDS ======================================
Benchmark/Database/sql_limit:15000_-8 36 32048808 ns/op
Benchmark/Sqlx_limit:15000_-8 28 41484578 ns/op
Benchmark/Sqlc_limit:15000_-8 34 31680017 ns/op
Benchmark/GORM_limit:15000_-8 20 59348697 ns/op
=================================================================================================
PASS
ok github.com/rexfordnyrk/go-db-comparison/benchmarks 77.835s
为了一致性和公平性,基准化分析在相同的硬件上运行。 测试还将每个操作放在单独的函数中并分别测量其性能来确保类似的代码结构。
有许多因素会影响生产服务器的性能,因此我们通常使用简单的基准化分析来尽可能消除外部因素。 虽然此基准化分析使用单条 select 语句,但我们鼓励您修改源代码并尝试更复杂的测试,例如使用连接查询和嵌套结构并获取更高的记录集,以便更好地模拟您自己的生产环境。 您可以使用 GitHub 上此仓库的 benchmarks 代码目录复制基准化分析。
每个结果集分为三列:
运行的基准化分析方法的名称。
在生成可靠时间之前基准化分析运行的次数。
每次执行基准化分析所花费的时间(纳秒)。
对于 1 个记录和 10 个记录的前两个测试,GORM 优于其他库。 但是,随着记录数的增加,它开始明显落后。 就性能而言,sqlx 一直排在第三位,比 GORM 更好,但当数据量增加时,它通常会落后于 sqlc 和 database/sql。
database/sql 和 sqlc 软件包在基准化分析的所有六种情况下都表现出色。 随着获取的记录数量增加(增加到 10,000 和 15,000 个记录),sqlc 比 database/sql 稍快。
结论
虽然 database/sql 是默认的 Golang 软件包,但您也应该视开发需求使用。 本文介绍了每个软件包的优点。
如果您需要高级查询、来自底层数据库技术的完整支持功能以及干净的代码库,那么 GORM 是最适合您的软件包 – 只要您愿意牺牲一些性能。 如果您只需要基本的查询并且愿意编写自己的 SQL,那么 database/sql 或 sqlx 软件包就足够了。
最后,sqlc 最适合大量使用数据库并需要在紧迫的期限内编写大量查询的后端开发者。 您可以编写原始 SQL 查询并生成代码,不必担心类型、扫描或其他影响工作效率的障碍。 sqlc 还提供了巨大的性能提升,尤其是在处理更大量的数据或记录集时。
请注意,由于基准化分析中存在错误,本文已更新。 非常感谢 Lukáš Zapletal 对原始文章发表的评论和提供的 bug 修正。 也感谢 JetBrains 社区提供这样的空间让大家共同学习和做出贡献。
相关文章:
golang-非orm数据库的操作与对比 database/sql、sqlx 和 sqlc
简单介绍 database/sql database/sql 是一个标准库软件包,负责与数据库(主要是 SQL 关系数据库)的连接和交互。 它为类 SQL 交互提供泛型接口、类型和方法。database/sql 在创建时将简单易用纳入考量,配置为支持与类 SQL 数据库…...
DeepSeek模型在非图形智能体的应用中是否需要GPU
答:不一定 概念 1、是否需要GPU与应用是否图形处理应用无关 2、文本内容智能体大多也需要GPU来提供更好的性能 3、DeepSeek模型在非图形智能体的应用中是否需要GPU取决于具体的模型版本和部署环境 不需要GPU的模型版本 DeepSeek-R1-1.5B: 这…...
RadioMaster POCKET遥控器进入ExpressLRS界面一直显示Loading的问题解决方法
RadioMaster POCKET遥控器进入ExpressLRS界面一直显示Loading的问题解决方法 问题描述解决方法 问题描述 有一天我发现我的 RadioMaster POCKET 遥控器进入 ExpressLRS 设置界面时,界面却一直停留在 “Loading” 状态,完全无法进入设置界面。 我并没有…...
idea的快捷键使用以及相关设置
文章目录 快捷键常用设置 快捷键 快捷键作用ctrlshift/注释选中内容Ctrl /注释一行/** Enter文档注释ALT SHIFT ↑, ALT SHIFT ↓上下移动当前代码Ctrl ALT L格式化代码Ctrl X删除所在行并复制该行Ctrl D复制当前行数据到下一行main/psvm快速生成入口程序soutSystem.o…...
【DDR 内存学习专栏 1.4 -- DDR 的 Bank Group】
文章目录 BankgroupBankgroup 与 Bank 的关系 DDR4 中的 BankgroupDDR4-3200 8Gb芯片为例组织结构访问场景 实际应用示例 Bankgroup Bankgroup是DDR4及后续标准(DDR5)中引入的一个更高层次的组织结构。它将多个Bank组合在一起形成一个Bankgroup,目的是为了进一步提…...
新晋前端框架技术:小程序容器与SuperApp构建
2025年,前端开发领域持续迭代,主流框架如Vue、React等纷纷推出新版本,在性能、开发效率及适用场景上实现突破,进一步巩固其技术地位。 1. Vue 3的全面普及与创新 Vue 3通过多项核心特性优化了开发体验: Teleport组件…...
强化学习:基于价值的方法做的是回归,基于策略的方法做的是分类,可以这么理解吗?
在强化学习领域,基于价值的方法(Value-based Methods)和基于策略的方法(Policy-based Methods)是两种核心范式。本文将从目标函数、优化机制以及与机器学习任务的类比角度,探讨这两种方法是否可以被分别理解为回归和分类任务,并深入分析其内在逻辑。 一、基于价值的方法…...
蓝耘元生代AIDC OS:一站式MaaS平台,助力AI应用快速落地
文章目录 引言1. 什么是MaaS平台?MaaS平台的典型特点 2. 蓝耘元生代AIDC OS 热门模型3. 快速入门:如何调用API?步骤1:注册并获取API Key步骤2:调用API(Python示例) 4. 与Chatbox搭配使用&#x…...
3.2.2.3 Spring Boot配置拦截器
在Spring Boot应用中配置拦截器(Interceptor)可以对请求进行预处理和后处理,实现如权限检查、日志记录等功能。通过实现HandlerInterceptor接口并注册到Spring容器,拦截器可以自动应用到匹配的请求路径。案例中,创建了…...
Python----机器学习(基于PyTorch的蘑菇逻辑回归)
Logistic Regression(逻辑回归)是一种用于处理二分类问题的统计学习方法。它基于线性回归 模型,通过Sigmoid函数将输出映射到[0, 1]范围内,表示概率。逻辑回归常被用于预测某个实 例属于正类别的概率。 一、数据集介绍 本例使用了…...
Python----机器学习(基于PyTorch的乳腺癌逻辑回归)
Logistic Regression(逻辑回归)是一种用于处理二分类问题的统计学习方法。它基于线性回归 模型,通过Sigmoid函数将输出映射到[0, 1]范围内,表示概率。逻辑回归常被用于预测某个实 例属于正类别的概率。 一、数据集介绍 在本例中&…...
如何配置AWS EKS自动扩展组:实现高效弹性伸缩
本文详细讲解如何在AWS EKS中配置节点组(Node Group)和Pod的自动扩展,优化资源利用率并保障应用高可用。 一、准备工作 工具安装 安装并配置AWS CLI 安装eksctl(EKS管理工具) 安装kubectl(Kubernetes命令…...
【C++ Qt】认识Qt、Qt 项目搭建流程(图文并茂、通俗易懂)
每日激励:“不设限和自我肯定的心态:I can do all things。 — Stephen Curry” 绪论: 本章将开启Qt的学习,Qt是一个较为古老但仍然在GUI图形化界面设计中有着举足轻重的地位,因为它适合嵌入式和多种平台而被广泛使用…...
用Python打造去中心化知识产权保护系统:科技驱动创作者权益新方案
用Python打造去中心化知识产权保护系统:科技驱动创作者权益新方案 近年来,区块链技术和去中心化系统的兴起为知识产权保护提供了新的可能性。在传统模式下,知识产权保护通常依赖于集中化管理机构,这种方式不仅成本高,还可能因不透明导致权益争议。于是,我们萌生了一个设…...
CVE重要漏洞复现-Fastjson1.2.24-RCE漏洞
本文仅供网络学习,不得用于非法目的,否则后果自负 1、漏洞简介 fastjson是阿里巴巴的开源JSON解析库,它可以解析JSON格式的字符串,也可以从JSON字符串反序列化到JavaBean。即fastjson的主要功能就是将Java Bean序列化成JSON字符…...
Windows 图形显示驱动开发-WDDM 1.2功能—显示设备硬件软件认证要求
一、容器技术id技术的硬件级实现要求 1.1 EDID规范深度适配 1.物理层要求: 必须使用EDID 2.0及以上版本数据结构 容器ID需写入VSDB区块的0x50-0x6F区域,采用Little-Endian格式存储 允许的最大传输延迟:I2C总线时钟频率≤100KHz时…...
Coze流搭建--写入飞书多维表格
目标 使用coze搭建一个业务流,将业务流生产出的数据写入飞书保存 测试业务流 使用图片生成插件,配置prompt生产图片,将生产的结果写入飞书文档 coze流 运行后最终效果 搭建流程 第一步:飞书创建多维表格 注册飞书创建多维表…...
4.14:计组第三章
一、数据的强制类型转换与存储 1、边界对齐与大端小端方式 2、真-强制类型转换 二、存储器的基本知识(不包含磁盘存储器) 1、主存储器 (1)...
Vue3+Vite前端项目部署后部分图片资源无法获取、动态路径图片资源报404错误的原因及解决方案
目录 Vue3vite前端项目部署后部分图片资源无法获取、动态路径图片资源报404错误的原因及解决方案 一、情景介绍 1、问题出现的场景 2、无法加载的图片写法 二、反向代理原理简介 三、造成该现象的原因 四、解决方案 1、放弃动态渲染 2、在页面挂载的时候引入图片资源 …...
Nacos操作指南
第一章:Nacos 概述 1.1 什么是 Nacos? 定义与定位 Nacos(Naming and Configuration Service)是阿里巴巴于2018年开源的动态服务发现、配置管理和服务管理平台,现已成为微服务生态中的重要基础设施。其核心价值在于帮…...
2025年常见渗透测试面试题-红队面试宝典下(题目+回答)
网络安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 一、Java反序列化过程及利用链示例 二、大型网络渗透经验 三、Cobalt Strike的两种Dump Hash区别 四…...
扩增子分析|基于R语言microeco包进行微生物群落网络分析(network网络、Zi-Pi关键物种和subnet子网络图)
一、引言 microeco包是福建农林大学姚敏杰教授团队开发的扩增子测序集成分析。该包综合了扩增子测序下游分析的多种功能包括群落组成、多样性、网络分析、零模型等等。通过简单的几行代码可实现复杂的分析。因此,microeco包发表以来被学界广泛关注,截止2…...
flutter-Text等组件出现双层黄色下划线的问题
文章目录 1. 现象2. 原因3. 解决方法 1. 现象 这天我正在写Flutter项目的页面功能,突然发现我的 Text 文字出现了奇怪的样式,具体如下: 文字下面出现了双层黄色下划线文字的空格变得很大,文字的间距也变得很大 我百思不得其解&a…...
优化运营、降低成本、提高服务质量的智慧物流开源了
智慧物流视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本可通过边缘计算技术…...
leetcode第二题
功能函数 typedef struct ListNode {int val;struct ListNode *next; } ListNode;struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2) {ListNode *dummy (ListNode *)malloc(sizeof(ListNode));ListNode *cur dummy;int carry 0; //carry是进位值…...
QT实现带快捷键的自定义 QComboBox 控件
在现代GUI应用程序中,用户界面的设计不仅要美观,还要提供高效的交互方式。本文将介绍一个自定义的QCComboBox类,它是一个基于Qt的组合框(QComboBox),支持为每个下拉项添加快捷键。通过这些快捷键࿰…...
聊聊类模板
我们来聊聊类模板,从基础到实际例子,让你更容易理解。 什么是类模板? 类模板是一种模板,允许我们定义一个可以处理任意数据类型的类。简单来说,就是我们可以编写一个类的“蓝图”,然后在需要的时候使用不…...
使用Python进行AI图像生成:从GAN到风格迁移的完整指南
AI图像生成是一个非常有趣且前沿的领域,结合了深度学习和计算机视觉技术。以下是一些使用Python和相关库进行AI图像生成的创意和实现思路: 1. 使用GAN(生成对抗网络) 基本概念:GAN由两个神经网络组成:生成…...
Java 设计模式:外观模式详解
Java 设计模式:外观模式详解 外观模式(Facade Pattern)是一种结构型设计模式,它为复杂的子系统提供一个简化的统一接口,隐藏子系统的复杂性,使客户端更方便地使用系统。外观模式就像一个“门面”ÿ…...
微信小程序中实现某个样式值setData改变时从350rpx到200rpx的平滑过渡效果
方案一:使用 CSS Transition(推荐简单场景) WXSS /* 在对应组件的WXSS中添加 */ .transition-effect {transition: all 0.4s ease-in-out;will-change: bottom; /* 启用GPU加速 */ }WXML <!-- 修改后的WXML --> <view class"…...
LINUX基础 [四] - Linux工具
目录 软件包管理器yum Linux开发工具vim vim的基本概念 vim的三种常用模式 vim的简单配置 vim常用模式的基本操作 命令模式 底行模式 处理vim打开文件报错的问题 Linux编译器-gcc/g使用 为什么我们可以用C/C做开发呢? 预处理(进行宏替换&#x…...
Spring Cloud之远程调用OpenFeign最佳实践
目录 OpenFeign最佳实践 问题引入 Feign 继承方式 创建Module 引入依赖 编写接口 打Jar包 服务提供方 服务消费方 启动服务并访问 Feign 抽取方式 创建Module 引入依赖 编写接口 打Jar包 服务消费方 启动服务并访问 服务部署 修改pom.xml文件 观察Nacos控制…...
【QT】 常用控件【输入类】
🌈 个人主页:Zfox_ 🔥 系列专栏:Qt 目录 一:🔥 输入类控件 🦋 Line Edit -- 单行输入框🎀 录入个人信息🎀 正则表达式验证输入框数据🎀 验证两次输入密码一致…...
【Python】读取xyz坐标文件输出csv文件
Python读取xyz坐标文件输出csv文件 import sys import numpy as np import pandas as pd from tqdm import tqdm import cv2 import argparsedef read_xyz(file_path):with open(file_path, "r") as f: # 打开文件data f.readlines() # 读取文件datas []for …...
深度解析Redis过期字段清理机制:从源码到集群化实践 (一)
深度解析Redis过期字段清理机制:从源码到集群化实践 一、问题本质与架构设计 1.1 过期数据管理的核心挑战 Redis连接池时序图技术方案 设计规范: #mermaid-svg-Yr9fBwszePgHNnEQ {font-family:"trebuchet ms",verdana,arial,sans-se…...
MapReduce实验:分析和编写WordCount程序(对文本进行查重)
实验环境:已经部署好的Hadoop环境 Hadoop安装、配置与管理_centos hadoop安装-CSDN博客 实验目的:对输入文件统计单词频率 实验过程: 1、准备文件 test.txt文件,它是你需要准备的原始数据文件,存放在你的 Linux 系…...
【中大厂面试题】腾讯云 java 后端 最新面试题
腾讯云(一面) 1. spring 和 springboot的区别是什么? 配置方式的区别:Spring 应用的配置较为繁琐,通常需要编写大量的 XML 配置文件或者使用 Java 注解进行配置。例如,配置数据源、事务管理器等都需要手动…...
Redis存储“大数据对象”的常用策略及StackOverflowError错误解决方案
Hi,大家好,我是灰小猿! 在一些功能的开发中,我们一般会有一些场景需要将得到的数据先暂时的存储起来,以便后面的接口或业务使用,这种场景我们一般常用的场景就是将数据暂时存储在缓存中,之后再…...
【Vue】v-if和v-show的区别
个人博客:haichenyi.com。感谢关注 一. 目录 一–目录二–核心区别三–使用场景四–性能对比五–总结 二. 核心区别 之前将css的显示隐藏的方式的时候,就已经提到过v-show和v-if了。忘记了的可以再回头去复习复习。 (2.1)…...
南瓜颜色预测:逻辑回归在农业分类问题中的实战应用
南瓜颜色预测:逻辑回归在农业分类问题中的实战应用 摘要 本案例通过预测南瓜颜色的分类问题,全面展示了逻辑回归在农业领域的实战应用。从数据预处理到模型评估,详细介绍了Seaborn可视化、模型构建、性能优化和结果解释等关键环节。案例不仅…...
【物联网-RS-485】
物联网-RS-485 ■ RS-485 连接方式■ RS-485 半双工通讯■ RS-485 的特点■ ModBus■ ModBus-ASCII■ ModBus-RTU ■ RS-485 连接方式 ■ RS-485 半双工通讯 一线定义为A 一线定义为B RS-485传输方式:半双工通信、(逻辑1:2V ~ 6V 逻辑0&…...
TDengine 语言连接器(Node.js)
简介 tdengine/websocket 是 TDengine 的官方 Node.js 语言连接器。Node.js 开发人员可以通过它开发存取 TDengine 数据库的应用软件。 Node.js 连接器源码托管在 GitHub。 Node.js 版本兼容性 支持 Node.js 14 及以上版本。 支持的平台 支持所有能运行 Node.js 的平台。 …...
Git分布式版本控制工具
一、工作流程 二、常用指令 1、配置git 配置环境变量 cmd打开命令行,输入git查看是否配置成功。 设置用户名和邮箱 git config --global user.name "用户名" git config --global user.email "邮箱" 查看用户名和邮箱 git config --glob…...
The first day of vue
关于小白直接接触vue3的第1天 首先我们需要一个脚手架node.js (这个可以从官网下载,免费的,安装也比较简单,后续我也会出一个相关的安装教程,方便大家和我一起讨论,互相学习) (不知道有没有人对…...
C语言超详细指针知识(三)
在经过前面两篇指针知识博客学习之后,我相信你已经对指针有了一定的理解,今天将更新C语言指针最后一篇,一起来学习吧。 1.字符指针变量 在指针类型的学习中,我们知道有一种指针类型为字符指针char*,之前我们是这样使用…...
无人机气动-结构耦合技术要点与难点
一、技术要点 1. 多学科耦合建模 气动载荷与结构响应的双向耦合:气动力(如升力、阻力、力矩)导致结构变形,而变形改变气动外形,进一步影响气流分布,形成闭环反馈。 建模方法: 高精度C…...
打造现代数据基础架构:MinIO对象存储完全指南
目录 打造现代数据基础架构:MinIO对象存储完全指南1. MinIO介绍1.1 什么是对象存储?1.2 MinIO核心特点1.3 MinIO使用场景 2. MinIO部署方案对比2.1 单节点单驱动器(SNSD/Standalone)2.2 单节点多驱动器(SNMD/Standalone Multi-Drive)2.3 多节点多驱动器(…...
SpringBoot条件注解全解析:核心作用与使用场景详解
目录 引言一、条件注解的核心机制二、SpringBoot内置条件注解详解1、ConditionalOnClass和ConditionalOnMissingClass2、ConditionalOnBean和ConditionalOnMissingBean3、ConditionalOnProperty4、ConditionalOnWebApplication和ConditionalOnNotWebApplication5、ConditionalO…...
智慧酒店企业站官网-前端静态网站模板【前端练习项目】
最近又写了一个静态网站,智慧酒店宣传官网。 使用的技术 html css js 。 特别适合编程学习者进行网页制作和前端开发的实践。 项目包含七个核心模块:首页、整体解决方案、优势、全国案例、行业观点、合作加盟、关于我们。 通过该项目,小伙伴们…...
#2 物联网组成要素
从下至上,则包括了5个要素,包括 设备 / 传感器 / 网络 / 物联网服务 / 数据分析 这五个要素。为了便于理解,我们用思维导图展示 物联网构成架构 设备 能够感测和反馈并连到网络进行物联网服务的装置 传感器 传感器和网关的融合实现了物…...