go-mysql
封装了 database/sql
的接口,所有接口与标准库功能保持一致,修复了 Go 标准库设计和实现层面的 bug。
如果使用 go-runner
启动服务,go-mysql
会被自动创建,无需操心。所有配置放在配置文件的 [mysql]
条目下。
配置需要填写以下信息:
[mysql]
dsn = "username:password@protocol(address)/dbname?param=value"
业务代码需要使用 MySQL 时,直接使用 New
方法即可。
import "git.altstory.com/altstory-framework/go-mysql"
func Foo(ctx context.Context, req *FooRequest) (res *FooResponse, err error) {
// 这里省略各种参数检查……
db := mysql.New(ctx)
rows, err := db.Query("SELECT uid FROM foo WHERE status = ?", status)
// 判断错误,使用 rows。具体代码省略……
}
原则上不推荐使用任何 ORM,比如 xorm、gorm 等,这些 ORM 副作用比较难以控制,且无法很好的根据 ctx 控制执行时间。
推荐使用 SQL builder 库来拼接 SQL,提升可控性并减少人工拼接的过程。推荐的库是 go-sqlbuilder。
配置里可以设置 dsn_slave
来指定一个从库,所有的读流量会走到从库,而写流量走主库。当这个配置没有设置时,所有流量都会走 dsn
指定的主库。
[mysql]
dsn = "username:password@protocol(address)/dbname?param=value"
dsn_slave = "username:password@protocol(address)/dbname?param=value"
默认情况下,所有的 MySQL#Query
/MySQL#QueryRow
/Stmt#Query
/Stmt#QueryRow
会走从库,所有的 Tx
/MySQL#Exec
/Stmt#Exec
会走主库。
如果需要强行走主库,可以使用以下代码来指定走主库。
row, err := mysql.UseMaster().QueryRow(sql, args)
在某些场景下,仅使用一个 MySQL 并不足够,那么我们可以自行构建 Factory
来连接更多的 MySQL 服务。
首先在配置文件中写一个新的 MySQL 连接配置。
[mysql_another]
dsn = "username:password@protocol(address)/dbname?param=value"
然后实例化一个新的工厂。
// anotherMySQLFactory 的类型是 **Factory,是一个指针的指针。
var anotherMySQLFactory = mysql.Register("mysql_another")
接着,使用这个全局变量 anotherMySQLFactory
来创建新的 MySQL client,在业务中使用。
factory := *anotherMySQLFactory
mysql := factory.New(ctx)
// 使用 mysql client 进行各种操作……
为了能够方便的进行 MySQL 扩容,go-mysql
支持配置多实例,从而让未来的 MySQL 扩容变得相对简单。
例如,在上线时我们先仅设置一个数据库信息。
[mysql]
mod = 1
[[mysql.instances]]
dsn = "username:password@protocol(address1)/dbname?param=value"
buckets = [0]
如果发现这个库扛不住,可以添加一个新的实例。这里需要注意,mod
直接设置成了 10
,这是为了方便未来在扩容的时候保持实例数据的稳定。
[mysql]
mod = 10
[[mysql.instances]]
dsn = "username:password@protocol(address1)/dbname?param=value"
buckets = [0, 1, 2, 3, 4]
[[mysql.instances]]
dsn = "username:password@protocol(address2)/dbname?param=value"
buckets = [5, 6, 7, 8, 9]
又过了一段时间,我们发现 address2
有些问题,需要分出更多的实例,可以直接通过修改 buckets
来分摊 address2
流量。
[mysql]
mod = 10
[[mysql.instances]]
dsn = "username:password@protocol(address1)/dbname?param=value"
buckets = [0, 1, 2, 3, 4]
[[mysql.instances]]
dsn = "username:password@protocol(address2)/dbname?param=value"
buckets = [5, 6, 7]
[[mysql.instances]]
dsn = "username:password@protocol(address3)/dbname?param=value"
buckets = [8, 9]
由于 MySQL 分流后,数据写入会分到不同的主库,如果我们需要再次合并这些数据库实例,则需要花费较长的时间合并数据到一个实例才行。这需要在运维的时候注意这个细节。
使用了集群配置之后,MySQL 的使用方法会发生变化,必须使用 mysql.WithIndex
来修饰 ctx
来选择实例。
// 假设我们拿到了一个数据库 hash id,需要将它转化成一个 int64 类型的数据并设置到 ctx 里面去。
// 如果 id 是 int64 类型,直接设置即可;
// 如果 id 是 string,推荐使用 hash/fnv 的 fnv.New64() 来获得一个 hash 来计算,这里做了个演示。
uuid := "xxxxxxxx-xxxxx-xxxxxxxx"
hash := fnv.New64()
io.WriteString(hash, uuid)
idx := hash.Sum64()
ctx = mysql.WithIndex(ctx, idx)
m := mysql.New(ctx)