# 配置文件
- 不同数据类型对应的 link 及驱动如下:
mysql
# mysql:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true
# import _ "github.com/gogf/gf/contrib/drivers/mysql/v2"
mariadb
# mariadb:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true
# import _ "github.com/gogf/gf/contrib/drivers/mariadb/v2"
tidb
# tidb:root:12345678@tcp(127.0.0.1:3306)/test?loc=Local&parseTime=true
# import _ "github.com/gogf/gf/contrib/drivers/tidb/v2"
pgsql
# pgsql:root:12345678@tcp(127.0.0.1:5432)/test
# import _ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
mssql
# mssql:root:12345678@tcp(127.0.0.1:1433)/test?encrypt=disable
# import _ "github.com/gogf/gf/contrib/drivers/mssql/v2"
sqlite
# sqlite::@file(/var/data/db.sqlite3)
# import _ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
# oracle
# oracle:root:12345678@tcp(127.0.0.1:5432)/test
# import _ "github.com/gogf/gf/contrib/drivers/oracle/v2"
clickhouse
# clickhouse:root:12345678@tcp(127.0.0.1:9000)/test
# import _ "github.com/gogf/gf/contrib/drivers/clickhouse/v2"
dm
# dm:root:12345678@tcp(127.0.0.1:5236)/test
# import _ "github.com/gogf/gf/contrib/drivers/dm/v2"
gaussdb
# gaussdb:root:12345678@tcp(127.0.0.1:5432)/test
# import _ "github.com/gogf/gf/contrib/drivers/gaussdb/v2"
- 时区处理:建议在配置中统一加上 locl 配置,例如: loc=Local&parseTime=true
database:
logger:
level: "all"
stdout: true
default:
link: "mysql:root:12345678@tcp(192.168.1.10:3306)/mydb?loc=Local&parseTime=true"
debug: true
order:
link: "mysql:root:12345678@tcp(192.168.1.20:3306)/order?loc=Local&parseTime=true"
debug: true
- 完整的 config.yaml 数据库配置项的数据格式形如下:
database:
default: # 分组名称,可自定义,默认为default
host: "127.0.0.1" # 地址
port: "3306" # 端口
user: "root" # 账号
pass: "your_password" # 密码
name: "your_database" # 数据库名称
type: "mysql" # 数据库类型(如:mariadb/tidb/mysql/pgsql/mssql/sqlite/oracle/clickhouse/dm)
link: "" # (可选)自定义数据库链接信息,当该字段被设置值时,以上链接字段(Host,Port,User,Pass,Name,Type)将失效
extra: "" # (可选)不同数据库的额外特性配置,由底层数据库driver定义,具体有哪些配置请查看具体的数据库driver介绍
role: "master" # (可选)数据库主从角色(master/slave),默认为master。如果不使用应用主从机制请不配置或留空即可。
debug: false # (可选)开启调试模式
prefix: "gf_" # (可选)表名前缀
dryRun: false # (可选)ORM空跑(只读不写)
charset: "utf8" # (可选)数据库编码(如: utf8mb4/utf8/gbk/gb2312),一般设置为utf8mb4。默认为utf8。
protocol: "tcp" # (可选)数据库连接协议,默认为TCP
weight: 100 # (可选)负载均衡权重,用于负载均衡控制,不使用应用层的负载均衡机制请置空
timezone: "Local" # (可选)时区配置,例如:Local
namespace: "" # (可选)用以支持个别数据库服务Catalog&Schema区分的问题,原有的Schema代表数据库名称,而NameSpace代表个别数据库服务的Schema
maxIdle: 10 # (可选)连接池最大闲置的连接数(默认10)
maxOpen: 100 # (可选)连接池最大打开的连接数(默认无限制)
maxLifetime: "30s" # (可选)连接对象可重复使用的时间长度(默认30秒)
maxIdleConnTime: "30s" # (可选,v2.10新增)连接池中空闲连接的最大生存时间(默认30秒)。可以通过配置文件或SetConnMaxIdleTime方法设置,避免长时间空闲连接占用资源。
queryTimeout: "0" # (可选)查询语句超时时长(默认无限制,同时受ctx超时时间影响)。值为time.Parse支持的格式,如30s, 1m。
execTimeout: "0" # (可选)写入语句超时时长(默认无限制,同时受ctx超时时间影响)。值为time.Parse支持的格式,如30s, 1m。
tranTimeout: "0" # (可选)事务处理超时时长(默认无限制,同时受ctx超时时间影响)。值为time.Parse支持的格式,如30s, 1m。
prepareTimeout: "0" # (可选)预准备SQL语句执行超时时长(默认无限制,同时受ctx超时时间影响)。值为time.Parse支持的格式,如30s, 1m。
createdAt: "created_at" # (可选)自动创建时间字段名称
updatedAt: "updated_at" # (可选)自动更新时间字段名称
deletedAt: "deleted_at" # (可选)软删除时间字段名称
timeMaintainDisabled: false # (可选)是否完全关闭时间更新特性,为true时CreatedAt/UpdatedAt/DeletedAt都将失效
- gdb 的配置支持集群模式,数据库配置中每一项分组配置均可以是多个节点,支持负载均衡权重策略,例如:
# 可以通过 g.DB() 和 g.DB("user") 获取对应的数据库连接对象
database:
# default 分组包含一主一从
default:
- link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
role: "master"
- link: "mysql:root:12345678@tcp(127.0.0.1:3306)/test"
role: "slave"
# user 分组包含一主两从
user:
- link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user"
role: "master"
- link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user"
role: "slave"
- link: "mysql:root:12345678@tcp(127.0.0.1:3306)/user"
role: "slave"
- GetAllConfig 方法用于获取所有数据库配置信息,方便进行配置验证、配置导出等业务操作
import (
"fmt"
"github.com/gogf/gf/v2/database/gdb"
)
func main() {
// 获取所有数据库配置
allConfig := gdb.GetAllConfig()
// 遍历所有配置分组
for groupName, configGroup := range allConfig {
fmt.Printf("数据库分组: %s\n", groupName)
// 遍历分组中的所有节点
for i, node := range configGroup {
fmt.Printf(" 节点 %d:\n", i+1)
fmt.Printf(" Host: %s\n", node.Host)
fmt.Printf(" Port: %s\n", node.Port)
fmt.Printf(" Name: %s\n", node.Name)
fmt.Printf(" Type: %s\n", node.Type)
fmt.Printf(" Role: %s\n", node.Role)
}
}
}
# 增删改查
# 事务处理
# 常规操作
常规的事务操作方法为 Begin/Commit/Rollback
db := g.DB()
// db.Begin 开启事务
if tx, err := db.Begin(ctx); err == nil {
r, err := tx.Save("user", g.Map{
"id" : 1,
"name" : "john",
})
if err != nil {
# 事务回滚
tx.Rollback()
}
if err == nil {
# 事务提交
tx.Commit()
}
fmt.Println(r)
}
# 闭包操作
通过常规的事务方法来管理事务有一些问题:冗余代码较多、操作风险较大等,为方便安全执行事务操作, ORM 组件同样提供了事务的闭包操作,通过 Transaction 方法实现,该方法定义如下:
// 当给定的闭包方法返回的 error 为 nil 时,那么闭包执行结束后当前事务自动执行 Commit 提交操作
// 否则自动执行 Rollback 回滚操作
// 如果闭包内部操作产生 panic 中断,该事务也将自动进行回滚,以保证操作安全
func (db DB) Transaction(ctx context.Context, f func(ctx context.Context, tx TX) error) (err error)
g.DB().Transaction(context.TODO(), func(ctx context.Context, tx gdb.TX) error {
result, err := tx.Ctx(ctx).Insert("user", g.Map{
"passport": "john",
"password": "12345678",
"nickname": "JohnGuo",
})
if err != nil {
return err
}
id, err := result.LastInsertId()
if err != nil {
return err
}
_, err = tx.Ctx(ctx).Insert("user_detail", g.Map{
"uid": id,
"site": "https://johng.cn",
"true_name": "GuoQiang",
})
if err != nil {
return err
}
return nil
})
# 事务传播
在复杂的业务场景中,一个事务方法可能会调用其他事务方法,此时需要明确定义事务的传播行为,以确保数据的一致性和完整性。
事务传播定义了事务方法被另一个事务方法调用时应该如何表现。
GoFrame ORM支持以下事务传播类型:
PropagationNested (嵌套事务,默认)
# 如果当前存在事务,则创建嵌套事务(使用保存点);如果不存在事务,则创建新事务
PropagationRequired (保证事务)
# 在当前存在事务时加入该事务,在没有事务时创建新事务,并不会创建嵌套事务
PropagationRequiresNew (创建新事务)
# 创建新事务,如果当前存在事务,则挂起当前事务
PropagationSupports (支持当前事务)
# 如果当前存在事务,则加入该事务;如果不存在事务,则以非事务方式执行
PropagationMandatory (强制使用事务)
# 如果当前存在事务,则加入该事务;如果不存在事务,则抛出异常
PropagationNever (不允许在事务中执行)
# 以非事务方式执行,如果当前存在事务,则抛出异常
PropagationNotSupported
# 以非事务方式执行,如果当前存在事务,则挂起当前事务
- PropagationNested (嵌套事务,默认)
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
"context"
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
func main() {
var (
ctx = context.Background()
db = g.DB()
)
db.SetDebug(true)
// 执行事务
err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
// 在外层事务中插入数据
_, err := tx.Insert("user", g.Map{
"id": 1,
"username": "outer_user",
})
if err != nil {
return err
}
// 嵌套事务 - 使用PropagationNested创建嵌套事务(使用保存点)
err = tx.Transaction(ctx, func(ctx context.Context, tx2 gdb.TX) error {
// 在嵌套事务中插入数据
_, err = tx2.Insert("user", g.Map{
"id": 2,
"username": "nested_user",
})
if err != nil {
return err
}
// 模拟错误,导致嵌套事务回滚到保存点
return fmt.Errorf("嵌套事务故意失败")
})
// 嵌套事务失败,但外层事务可以继续
fmt.Println("嵌套事务错误:", err)
// 继续在外层事务中插入数据
_, err = tx.Insert("user", g.Map{
"id": 3,
"username": "outer_after_nested",
})
// 外层事务正常提交
return nil
})
if err != nil {
fmt.Println("事务执行失败:", err)
return
}
// 查询结果
result, err := db.Model("user").All()
if err != nil {
fmt.Println("查询失败:", err)
return
}
// 数据中只有id为1和3的数据,没有2
fmt.Println("查询结果:", result)
}
// 上述代码执行的SQL语句类似于:
-- 开始外层事务
BEGIN;
-- 插入外层数据
INSERT INTO user(id,username) VALUES(1,'outer_user');
-- 创建保存点
SAVEPOINT sp1;
-- 插入嵌套事务数据
INSERT INTO user(id,username) VALUES(2,'nested_user');
-- 嵌套事务失败,回滚到保存点
ROLLBACK TO SAVEPOINT sp1;
-- 继续外层事务
INSERT INTO user(id,username) VALUES(3,'outer_after_nested');
-- 提交外层事务
COMMIT;
- PropagationRequired (保证事务)
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
"context"
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
func main() {
var (
ctx = context.Background()
db = g.DB()
)
db.SetDebug(true)
// 执行事务
err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
// 在外层事务中插入数据
// 在事务闭包中使用tx对象或者db对象来操作数据表都是等价的
_, err := tx.Insert("user", g.Map{
"id": 1,
"username": "outer_user",
})
if err != nil {
return err
}
// 嵌套事务 - 默认使用PropagationRequired
err = tx.TransactionWithOptions(ctx, gdb.TxOptions{
Propagation: gdb.PropagationRequired,
}, func(ctx context.Context, tx2 gdb.TX) error {
// 在嵌套事务中插入数据(使用相同的事务)
// 在事务闭包中使用tx2对象或者db对象来操作数据表都是等价的
_, err = tx2.Insert("user", g.Map{
"id": 2,
"username": "inner_user",
})
return err
})
return err
})
if err != nil {
fmt.Println("事务执行失败:", err)
return
}
// 查询结果
result, err := db.Model("user").All()
if err != nil {
fmt.Println("查询失败:", err)
return
}
fmt.Println("查询结果:", result)
}
// 上述代码执行的SQL语句类似于
-- 开始外层事务
BEGIN;
-- 插入外层数据
INSERT INTO user(id,username) VALUES(1,'outer_user');
-- 插入内层数据(使用相同事务)
INSERT INTO user(id,username) VALUES(2,'inner_user');
-- 提交事务
COMMIT;
- PropagationRequiresNew (创建新事务)
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
"context"
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
func main() {
var (
ctx = context.Background()
db = g.DB()
)
db.SetDebug(true)
// 执行事务
err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
// 在外层事务中插入数据
_, err := tx.Insert("user", g.Map{
"id": 1,
"username": "outer_user",
})
if err != nil {
return err
}
// 嵌套事务 - 使用PropagationRequiresNew创建新事务
err = tx.TransactionWithOptions(ctx, gdb.TxOptions{
Propagation: gdb.PropagationRequiresNew,
}, func(ctx context.Context, tx2 gdb.TX) error {
// 在新事务中插入数据
_, err = tx2.Insert("user", g.Map{
"id": 2,
"username": "new_tx_user",
})
// 模拟错误,导致内层事务回滚
return fmt.Errorf("内层事务故意失败")
})
// 内层事务失败不影响外层事务
fmt.Println("内层事务错误:", err)
// 继续在外层事务中插入数据
_, err = tx.Insert("user", g.Map{
"id": 3,
"username": "outer_after_error",
})
// 外层事务正常提交
return nil
})
if err != nil {
fmt.Println("事务执行失败:", err)
return
}
// 查询结果
result, err := db.Model("user").All()
if err != nil {
fmt.Println("查询失败:", err)
return
}
fmt.Println("查询结果:", result)
}
// 上述代码执行的SQL语句类似于:
-- 开始外层事务
BEGIN;
-- 插入外层数据
INSERT INTO user(id,username) VALUES(1,'outer_user');
-- 开始新的独立事务
BEGIN;
-- 插入内层数据(使用新事务)
INSERT INTO user(id,username) VALUES(2,'new_tx_user');
-- 内层事务回滚
ROLLBACK;
-- 继续外层事务
INSERT INTO user(id,username) VALUES(3,'outer_after_error');
-- 提交外层事务
COMMIT;
- PropagationSupports (支持当前事务)
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
"context"
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
func main() {
var (
ctx = context.Background()
db = g.DB()
)
db.SetDebug(true)
// 场景1: 有外部事务时,加入外部事务
err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
// 在外层事务中插入数据
_, err := tx.Insert("user", g.Map{
"id": 1,
"username": "outer_user",
})
if err != nil {
return err
}
// 嵌套事务 - 使用PropagationSupports
err = tx.TransactionWithOptions(ctx, gdb.TxOptions{
Propagation: gdb.PropagationSupports,
}, func(ctx context.Context, tx2 gdb.TX) error {
// 在支持当前事务的方式下插入数据(使用外层事务)
_, err = tx2.Insert("user", g.Map{
"id": 2,
"username": "supports_user",
})
return err
})
return err
})
if err != nil {
fmt.Println("场景1执行失败:", err)
return
}
// 查询结果
result, err := db.Model("user").All()
if err != nil {
fmt.Println("查询失败:", err)
return
}
fmt.Println("场景1查询结果:", result)
// 清空数据表
_, err = db.Exec(ctx, `TRUNCATE TABLE user`)
if err != nil {
fmt.Println("执行失败:", err)
return
}
// 场景2: 没有外部事务时,非事务方式执行
err = db.TransactionWithOptions(ctx, gdb.TxOptions{
Propagation: gdb.PropagationSupports,
}, func(ctx context.Context, tx gdb.TX) error {
// 以非事务方式插入数据
_, err = tx.Insert("user", g.Map{
"id": 3,
"username": "non_tx_user",
})
return err
})
if err != nil {
fmt.Println("场景2执行失败:", err)
return
}
// 查询结果
result, err = db.Model("user").All()
if err != nil {
fmt.Println("查询失败:", err)
return
}
fmt.Println("场景2查询结果:", result)
}
// 上述代码执行的SQL语句类似于:
-- 场景1: 有外部事务
-- 开始外层事务
BEGIN;
-- 插入外层数据
INSERT INTO user(id,username) VALUES(1,'outer_user');
-- 插入内层数据(使用外层事务)
INSERT INTO user(id,username) VALUES(2,'supports_user');
-- 提交事务
COMMIT;
-- 场景2: 没有外部事务
-- 非事务方式直接插入数据
INSERT INTO user(id,username) VALUES(3,'non_tx_user');
- PropagationMandatory (强制使用事务)
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
"context"
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
func main() {
var (
ctx = context.Background()
db = g.DB()
)
db.SetDebug(true)
// 场景1: 有外部事务时,加入外部事务
err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
// 在外层事务中插入数据
_, err := tx.Insert("user", g.Map{
"id": 1,
"username": "outer_user",
})
if err != nil {
return err
}
// 嵌套事务 - 使用PropagationMandatory
err = tx.TransactionWithOptions(ctx, gdb.TxOptions{
Propagation: gdb.PropagationMandatory,
}, func(ctx context.Context, tx2 gdb.TX) error {
// 在强制使用当前事务的方式下插入数据(使用外层事务)
_, err = tx2.Insert("user", g.Map{
"id": 2,
"username": "mandatory_user",
})
return err
})
return err
})
if err != nil {
fmt.Println("场景1执行失败:", err)
return
}
// 查询结果
result, err := db.Model("user").All()
if err != nil {
fmt.Println("查询失败:", err)
return
}
fmt.Println("场景1查询结果:", result)
// 清空数据表
_, err = db.Exec(ctx, `TRUNCATE TABLE user`)
if err != nil {
fmt.Println("执行失败:", err)
return
}
// 场景2: 没有外部事务时,将抛出异常
fmt.Println("场景2: 没有外部事务时使用PropagationMandatory")
err = db.TransactionWithOptions(ctx, gdb.TxOptions{
Propagation: gdb.PropagationMandatory,
}, func(ctx context.Context, tx gdb.TX) error {
// 这里的代码不会执行,因为没有外部事务时会抛出异常
_, err = tx.Insert("user", g.Map{
"id": 3,
"username": "will_not_insert",
})
return err
})
// 应该有错误,因为没有外部事务
fmt.Println("场景2错误:", err)
}
// 上述代码执行的SQL语句类似于:
-- 场景1: 有外部事务
-- 开始外层事务
BEGIN;
-- 插入外层数据
INSERT INTO user(id,username) VALUES(1,'outer_user');
-- 插入内层数据(使用外层事务)
INSERT INTO user(id,username) VALUES(2,'mandatory_user');
-- 提交事务
COMMIT;
-- 场景2: 没有外部事务
-- 抛出异常: "mandatory transaction is required, but none exists"
- PropagationNever (不允许在事务中执行)
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
"context"
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
func main() {
var (
ctx = context.Background()
db = g.DB()
)
db.SetDebug(true)
// 场景1: 有外部事务时,将抛出异常
fmt.Println("场景1: 有外部事务时使用PropagationNever")
err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
// 在外层事务中插入数据
_, err := tx.Insert("user", g.Map{
"id": 1,
"username": "outer_user",
})
if err != nil {
return err
}
// 嵌套事务 - 使用PropagationNever
err = tx.TransactionWithOptions(ctx, gdb.TxOptions{
Propagation: gdb.PropagationNever,
}, func(ctx context.Context, tx2 gdb.TX) error {
// 这里的代码不会执行,因为在外部事务中使用PropagationNever会抛出异常
_, err := tx2.Insert("user", g.Map{
"id": 2,
"username": "will_not_insert",
})
return err
})
// 应该有错误
fmt.Println("嵌套事务错误:", err)
// 继续外层事务
return nil
})
if err != nil {
fmt.Println("场景1执行失败:", err)
return
}
// 查询结果
result, err := db.Model("user").All()
if err != nil {
fmt.Println("查询失败:", err)
return
}
// 应该只有id=1的记录
fmt.Println("场景1查询结果:", result)
// 清空数据表
_, err = db.Exec(ctx, `TRUNCATE TABLE user`)
if err != nil {
fmt.Println("执行失败:", err)
return
}
// 场景2: 没有外部事务时,非事务方式执行
err = db.TransactionWithOptions(ctx, gdb.TxOptions{
Propagation: gdb.PropagationNever,
}, func(ctx context.Context, tx gdb.TX) error {
// 以非事务方式插入数据
_, err = tx.Insert("user", g.Map{
"id": 3,
"username": "non_tx_user",
})
return err
})
if err != nil {
fmt.Println("场景2执行失败:", err)
return
}
// 查询结果
result, err = db.Model("user").All()
if err != nil {
fmt.Println("查询失败:", err)
return
}
// 应该有id=3的记录
fmt.Println("场景2查询结果:", result)
}
// 上述代码执行的SQL语句类似于:
-- 场景1: 有外部事务
-- 开始外层事务
BEGIN;
-- 插入外层数据
INSERT INTO user(id,username) VALUES(1,'outer_user');
-- 抛出异常: "transaction is existing, but never transaction is required"
-- 提交外层事务
COMMIT;
-- 场景2: 没有外部事务
-- 非事务方式直接插入数据
INSERT INTO user(id,username) VALUES(3,'non_tx_user');
- PropagationNotSupported (非事务方式执行)
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
"context"
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
func main() {
var (
ctx = context.Background()
db = g.DB()
)
db.SetDebug(true)
// 执行事务
err := db.Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
// 在事务中插入数据
_, err := tx.Insert("user", g.Map{
"id": 1,
"username": "tx_user",
})
if err != nil {
return err
}
// 使用PropagationNotSupported挂起当前事务
err = tx.TransactionWithOptions(ctx, gdb.TxOptions{
Propagation: gdb.PropagationNotSupported,
}, func(ctx context.Context, tx2 gdb.TX) error {
// 非事务方式写入数据
_, err = tx2.Insert("user", g.Map{
"id": 2,
"username": "non_tx_user",
})
return err
})
if err != nil {
return err
}
// 模拟错误,导致外层事务回滚
return fmt.Errorf("外层事务故意失败")
})
fmt.Println("事务执行结果:", err)
// 查询结果
result, err := db.Model("user").All()
if err != nil {
fmt.Println("查询失败:", err)
return
}
// 应该只看到id=2的记录,因为id=1的记录在事务回滚时被撤销
fmt.Println("查询结果:", result)
}
// 上述代码执行的SQL语句类似于:
-- 开始事务
BEGIN;
-- 插入事务内数据
INSERT INTO user(id,username) VALUES(1,'tx_user');
-- 非事务方式执行(直接提交)
INSERT INTO user(id,username) VALUES(2,'non_tx_user');
-- 外层事务回滚
ROLLBACK;
# 隔离级别
- 事务隔离级别主要解决并发事务执行时可能出现的以下问题:
脏读(Dirty Read)# 一个事务读取了另一个未提交事务修改过的数据
不可重复读(Non-repeatable Read)# 在同一事务内,多次读取同一数据返回的结果有所不同
幻读(Phantom Read)# 在同一事务内,多次执行同一查询返回不同的数据集(行数变化)
- 不同的隔离级别适用于不同的应用场景:
读未提交(Read Uncommitted)
# 性能最好,没有锁开销,并发度最高
# 对数据一致性要求极低的场景
读已提交(Read Committed)
# 避免脏读问题,较好的并发性能,大多数数据库的默认级别
# 需要避免脏读但可以接受不可重复读的场景
可重复读(Repeatable Read)
# 避免脏读和不可重复读问题,MySQL 的默认隔离级别
# 对数据一致性要求较高的业务逻辑,财务类应用
串行化(Serializable)
# 完全避免并发问题,提供最高级别的数据一致性保证
# 对数据一致性要求极高的关键业务,银行转账等金融核心交易
- 读已提交(Read Committed)
// 使用读已提交隔离级别
tx, err := db.BeginWithOptions(ctx, gdb.TxOptions{
Isolation: sql.LevelReadCommitted,
})
if err != nil {
return
}
defer tx.Rollback()
// 查询用户余额
balance, err := tx.Model("account").Where("user_id", 1).Value("balance")
// SQL: SELECT balance FROM account WHERE user_id=1
// 更新余额
_, err = tx.Model("account").Where("user_id", 1).Update(g.Map{"balance": balance.Int() + 100})
// SQL: UPDATE account SET balance=balance+100 WHERE user_id=1
if err = tx.Commit(); err != nil {
return
}
- 可重复读(Repeatable Read)
// 使用可重复读隔离级别(MySQL默认)
tx, err := db.BeginWithOptions(ctx, gdb.TxOptions{
Isolation: sql.LevelRepeatableRead,
})
if err != nil {
return
}
defer tx.Rollback()
// 第一次查询用户数据
user1, err := tx.Model("user").Where("id", 1).One()
// SQL: SELECT * FROM user WHERE id=1
// ... 其他操作
// 第二次查询相同用户数据,在可重复读级别下,即使其他事务修改了该数据,这里读取的结果仍与第一次相同
user2, err := tx.Model("user").Where("id", 1).One()
// SQL: SELECT * FROM user WHERE id=1
if err = tx.Commit(); err != nil {
return
}
- 串行化(Serializable)
// 使用串行化隔离级别
tx, err := db.BeginWithOptions(ctx, gdb.TxOptions{
Isolation: sql.LevelSerializable,
})
if err != nil {
return
}
defer tx.Rollback()
// 查询满足条件的所有用户
users, err := tx.Model("user").Where("status", "active").All()
// SQL: SELECT * FROM user WHERE status='active'
// 在串行化级别下,其他事务无法同时修改或添加符合此条件的记录
// 这确保了在事务执行期间,查询结果的一致性
if err = tx.Commit(); err != nil {
return
}
- 使用事务闭包函数,可以同时指定隔离级别
// 使用可重复读隔离级别处理订单
err := db.TransactionWithOptions(ctx, gdb.TxOptions{
Isolation: sql.LevelRepeatableRead,
}, func(ctx context.Context, tx gdb.TX) error {
// 1. 查询商品库存
stock, err := tx.Model("product").Where("id", productId).Value("stock")
// SQL: SELECT stock FROM product WHERE id=?
if err != nil {
return err
}
// 2. 检查库存是否充足
if stock.Int() < quantity {
return errors.New("库存不足")
}
// 3. 创建订单
orderId, err := tx.Model("order").InsertAndGetId(g.Map{
"user_id": userId,
"product_id": productId,
"quantity": quantity,
"status": "pending",
})
// SQL: INSERT INTO order(user_id,product_id,quantity,status) VALUES(?,?,?,'pending')
if err != nil {
return err
}
// 4. 减少库存
_, err = tx.Model("product").Where("id", productId).
Update(g.Map{"stock": gdb.Raw("stock - ?", quantity)})
// SQL: UPDATE product SET stock=stock-? WHERE id=?
return err
})
# 结果处理
- 结果类型
type Value = *gvar.Var # 返回数据表记录值
type Record map[string]Value # 返回数据表记录键值对
type Result []Record # 返回数据表记录列表
- 为空判断
// 数据集合(多条)
r, err := g.Model("order").Where("status", 1).All()
if err != nil {
return err
}
if len(r) == 0 {
// 结果为空
}
if r.IsEmpty() {
// 结果为空
}
// 数据记录(单条)
r, err := g.Model("order").Where("status", 1).One()
if err != nil {
return err
}
if len(r) == 0 {
// 结果为空
}
if r.IsEmpty() {
// 结果为空
}
// 数据字段值,返回的是一个"泛型"变量,这个只能使用 IsEmpty 来判断是否为空了
r, err := g.Model("order").Where("status", 1).Value()
if err != nil {
return err
}
if r.IsEmpty() {
// 结果为空
}
// 字段值数组,返回字段值数组本身类型为 []gdb.Value 类型,因此直接判断长度是否为 0 即可
r, err := g.Model("order").Fields("id").Where("status", 1).Array()
if err != nil {
return err
}
if len(r) == 0 {
// 结果为空
}
// Struct 对象
// 传递的对象是一个空指针时
var user *User
err := g.Model("order").Where("status", 1).Scan(&user)
if err != nil {
return err
}
if user == nil {
// 结果为空
}
// 传递的对象是一个初始化的对象
var user = new(User)
err := g.Model("order").Where("status", 1).Scan(&user)
if err != nil && err != sql.ErrNoRows {
return err
}
if err == sql.ErrNoRows {
// 结果为空
}
// Struct 数组
// 当传递的对象数组本身是一个空数组
var users []*User
err := g.Model("order").Where("status", 1).Scan(&users)
if err != nil {
return err
}
if len(users) == 0 {
// 结果为空
}
// 当传递的对象数组本身不是空数组
var users = make([]*User, 100)
err := g.Model("order").Where("status", 1).Scan(&users)
if err != nil {
return err
}
if err == sql.ErrNoRows {
// 结果为空
}
- 空数组结构返回
// 大部分场景下,查询的数据需要展示在浏览器页面上,也就意味着返回的数据需要给前端 JS 进行处理
// 为了前端处理返回数据时更加友好,在后端查询不到数据时,期望返回一个空的数组结构,而不是返回 null
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
"fmt"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
func main() {
type User struct {
Id uint64 // 主键
Passport string // 账号
Password string // 密码
NickName string // 昵称
CreatedAt *gtime.Time // 创建时间
UpdatedAt *gtime.Time // 更新时间
}
type Response struct {
Users []User
}
var res = &Response{
Users: make([]User, 0),
}
err := g.Model("user").WhereGT("id", 10).Scan(&res.Users)
fmt.Println(err)
fmt.Println(gjson.MustEncodeString(res))
}
# 分库分表
# ORM分表(Table Sharding)
解决单表数据量过大的问题,通过将数据分散到不同的表,可以显著提高查询性能
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// User 用户结构体
type User struct {
Id int `json:"id"`
Name string `json:"name"`
}
func main() {
// 创建分表配置
shardingConfig := gdb.ShardingConfig{
Table: gdb.ShardingTableConfig{
Enable: true, // 启用分表
Prefix: "user_", // 分表前缀
Rule: &gdb.DefaultShardingRule{
TableCount: 4, // 分表数量
},
},
}
// 准备测试数据
user := User{
Id: 1,
Name: "John",
}
// 创建分表模型
db := g.DB()
db.SetDebug(true)
model := db.Model("user").
Sharding(shardingConfig).
ShardingValue(user.Id) // 使用用户ID作为分片值
// 插入数据
_, err := model.Data(user).Insert()
if err != nil {
panic(err)
}
// INSERT INTO `user_1`(`id`,`name`) VALUES(1,'John')
// 查询数据
var result User
err = model.Where("id", user.Id).Scan(&result)
if err != nil {
panic(err)
}
// SELECT * FROM `user_1` WHERE `id`=1 LIMIT 1
g.DumpJson(result)
// 更新数据
_, err = model.Data(g.Map{"name": "John Doe"}).
Where("id", user.Id).
Update()
if err != nil {
panic(err)
}
// UPDATE `user_1` SET `name`='John Doe' WHERE `id`=1
// 删除数据
_, err = model.Where("id", user.Id).Delete()
if err != nil {
panic(err)
}
// DELETE FROM `user_1` WHERE `id`=1
}
- 自定义分表规则,可以通过实现ShardingRule接口来自定义分表规则:
package main
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
"context"
"fmt"
"time"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gtime"
)
// TimeShardingRule 按时间分表的规则
type TimeShardingRule struct{}
// TableName 实现按月份分表的规则
func (r *TimeShardingRule) TableName(ctx context.Context, config gdb.ShardingTableConfig, value any) (string, error) {
// 将分片值转换为时间
t, ok := value.(time.Time)
if !ok {
return "", fmt.Errorf("sharding value must be time.Time for TimeShardingRule")
}
// 按年月生成表名,例如: log_202501
return fmt.Sprintf("%s%04d%02d", config.Prefix, t.Year(), t.Month()), nil
}
// SchemaName 实现分库规则接口
func (r *TimeShardingRule) SchemaName(ctx context.Context, config gdb.ShardingSchemaConfig, value any) (string, error) {
// 这里不实现分库,返回空字符串
return "", nil
}
func main() {
// 创建按时间分表的配置
shardingConfig := gdb.ShardingConfig{
Table: gdb.ShardingTableConfig{
Enable: true, // 启用分表
Prefix: "log_", // 分表前缀
Rule: &TimeShardingRule{}, // 自定义分表规则
},
}
// 当前时间作为分片值
now := gtime.Now().Time
// 创建分表模型
db := g.DB()
db.SetDebug(true)
model := db.Model("log").
Sharding(shardingConfig).
ShardingValue(now) // 使用时间作为分片值
// 插入日志数据
_, err := model.Data(g.Map{
"content": "系统启动",
"level": "info",
"time": now,
}).Insert()
if err != nil {
panic(err)
}
// INSERT INTO `log_202503`(`content`,`level`,`time`)
// VALUES('系统启动','info','2025-03-13 12:02:54')
}
# ORM分库(Schema Sharding)
是实现数据库水平扩展的重要手段,通过将数据分散到不同的数据库节点,可以显著提高系统的处理能力
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// User 用户结构体
type User struct {
Id int `json:"id"`
Name string `json:"name"`
}
func main() {
// 创建分库配置
shardingConfig := gdb.ShardingConfig{
Schema: gdb.ShardingSchemaConfig{
Enable: true, // 启用分库
Prefix: "db_", // 分库前缀
Rule: &gdb.DefaultShardingRule{
SchemaCount: 2, // 分库数量
},
},
}
// 准备测试数据
user := User{
Id: 1,
Name: "John",
}
// 创建分库模型
db := g.DB()
db.SetDebug(true)
model := db.Model("user").
Sharding(shardingConfig).
ShardingValue(user.Id) // 使用用户ID作为分片值
// 插入数据
_, err := model.Data(user).Insert()
if err != nil {
panic(err)
}
// INSERT INTO `user`(`id`,`name`) VALUES(1,'John')
// 注意:实际操作的是 db_1 数据库中的 user 表
// 查询数据
var result User
err = model.Where("id", user.Id).Scan(&result)
if err != nil {
panic(err)
}
// SELECT * FROM `user` WHERE `id`=1 LIMIT 1
// 注意:实际查询的是 db_1 数据库中的 user 表
g.DumpJson(result)
// 更新数据
_, err = model.Data(g.Map{"name": "John Doe"}).
Where("id", user.Id).
Update()
if err != nil {
panic(err)
}
// UPDATE `user` SET `name`='John Doe' WHERE `id`=1
// 注意:实际更新的是 db_1 数据库中的 user 表
// 删除数据
_, err = model.Where("id", user.Id).Delete()
if err != nil {
panic(err)
}
// DELETE FROM `user` WHERE `id`=1
// 注意:实际删除的是 db_1 数据库中的 user 表
}
- 自定义分库规则,可以通过实现ShardingRule接口来自定义分库规则:
package main
import (
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
"context"
"fmt"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
)
// RegionShardingRule 按地区分库的规则
type RegionShardingRule struct {
// 地区到数据库的映射
RegionMapping map[string]string
}
// SchemaName 实现按地区分库的规则
func (r *RegionShardingRule) SchemaName(ctx context.Context, config gdb.ShardingSchemaConfig, value any) (string, error) {
// 将分片值转换为地区信息
region, ok := value.(string)
if !ok {
return "", fmt.Errorf("sharding value must be string for RegionShardingRule")
}
// 获取地区对应的数据库名
if dbName, exists := r.RegionMapping[region]; exists {
return dbName, nil
}
// 如果没有找到对应的地区,使用默认数据库
return config.Prefix + "default", nil
}
// TableName 实现分表规则接口
func (r *RegionShardingRule) TableName(ctx context.Context, config gdb.ShardingTableConfig, value any) (string, error) {
// 这里不实现分表,返回空字符串
return "", nil
}
func main() {
// 创建地区到数据库的映射
regionMapping := map[string]string{
"east": "db_east",
"west": "db_west",
"north": "db_north",
"south": "db_south",
}
// 创建按地区分库的配置
shardingConfig := gdb.ShardingConfig{
Schema: gdb.ShardingSchemaConfig{
Enable: true, // 启用分库
Prefix: "db_", // 分库前缀
Rule: &RegionShardingRule{RegionMapping: regionMapping}, // 自定义分库规则
},
}
// 分片值为用户所在地区
region := "east"
// 创建分库模型
db := g.DB()
db.SetDebug(true)
model := g.DB().Model("user").
Sharding(shardingConfig).
ShardingValue(region) // 使用地区作为分片值
// 插入用户数据
_, err := model.Data(g.Map{
"id": 1001,
"name": "John",
"region": region,
}).Insert()
if err != nil {
panic(err)
}
// INSERT INTO `user`(`id`,`name`,`region`) VALUES(1001,'John','east')
// 注意:实际操作的是 db_east 数据库中的 user 表
}
# 结合分库分表
GoFrame ORM支持同时配置分库和分表,实现更精细的数据分片:
// 同时配置分库和分表
shardingConfig := gdb.ShardingConfig{
Schema: gdb.ShardingSchemaConfig{
Enable: true, // 启用分库
Prefix: "db_", // 分库前缀
Rule: &gdb.DefaultShardingRule{
SchemaCount: 2, // 分库数量
},
},
Table: gdb.ShardingTableConfig{
Enable: true, // 启用分表
Prefix: "user_", // 分表前缀
Rule: &gdb.DefaultShardingRule{
TableCount: 4, // 分表数量
},
},
}
// 使用分库分表配置
model := g.DB().Model("user").
Sharding(shardingConfig).
ShardingValue(10001) // 同一个分片值用于计算分库和分表
← 开发流程