dbx支持緩存全表數(shù)據(jù)的高性能 Golang DB 庫
什么是 dbx ? 簡而言之就是:
dbx = DB + Cache
它是一個支持對全表數(shù)據(jù)進(jìn)行透明緩存的 Golang DB 庫,在內(nèi)存足夠大的情況下,不再需要 Memcached, Redis 等緩存服務(wù)。 而且讀取緩存的速度相當(dāng)之快,本機(jī)測試 qps 達(dá)到: 350萬+/秒,可以有效的簡化應(yīng)用端業(yè)務(wù)邏輯代碼。
它支持 MySQL/Sqlite3,支持結(jié)構(gòu)體自由組合嵌套。
它的實(shí)現(xiàn)原理為自動掃描表結(jié)構(gòu),確定主鍵和自增列,并且通過主鍵按照行來緩存數(shù)據(jù),按照行透明管理 cache,上層只需要按照普通的 ORM 風(fēng)格 API 操作即可。
支持緩存,高性能讀取 KV 緩存全表數(shù)據(jù)
經(jīng)過本機(jī)簡單的測試(小數(shù)據(jù)下),直接查詢 Sqlite3 速度可以達(dá)到 3萬+/秒,開啟緩存后達(dá)到恐怖的 350萬+/秒。 一般針對高頻訪問的小表開啟緩存:
db.Bind("user", &User{}, true)
db.Bind("group", &Group{}, true)
db.EnableCache(true)
支持嵌套,盡量避免低效反射
golang 為靜態(tài)語言,在實(shí)現(xiàn)比較復(fù)雜的功能的時候往往要用到反射,而反射使用不當(dāng)?shù)臅r候會嚴(yán)重拖慢速度。經(jīng)過實(shí)踐發(fā)現(xiàn),應(yīng)該盡量使用數(shù)字索引,不要使用字符串索引,比如 Field() 性能大約是 FieldByName() 的 50 倍! 絕大部分 db 庫不支持嵌套,因?yàn)榉瓷溆致謴?fù)雜,特別是嵌套層數(shù)過多的時候。還好通過努力,dbx 高效的實(shí)現(xiàn)了無限制層數(shù)的嵌套支持,并且性能還不錯。
type Human struct {
Age int64 `db:"age"`
}
type User struct {
Human
Uid int64 `db:"uid"`
Gid int64 `db:"gid"`
Name string `db:"name"`
CreateDate time.Time `db:"createDate"`
}
API 預(yù)覽:
通過 golang 的反射特性,可以實(shí)現(xiàn)接近腳本語言級的便捷程度。如下:
// 打開數(shù)據(jù)庫
db, err = dbx.Open("mysql", "root@tcp(localhost)/test?parseTime=true&charset=utf8")
// 插入一條
db.Table("user").Insert(user)
// 查詢一條
db.Table("user").Where("uid=?", 1).One(&user)
// 通過主健查詢一條
db.Table("user").WherePK(1).One(&user)
// 通過主健更新一條
db.Table("user").Update(&user)
// 通過主健刪除一條
db.Table("user").WherePK(1).Delete()
// 獲取多條
db.Table("user").Where("uid>?", 1).All(&userList)
日志輸出到指定的流
可以自由的重定向日志數(shù)據(jù)流。
// 將 db 產(chǎn)生的錯誤信息輸出到標(biāo)準(zhǔn)輸出(控制臺)
db.Stderr = os.Stdout
// 將 db 產(chǎn)生的錯誤信息輸出到指定的文件
db.Stderr = dbx.OpenFile("./db_error.log")
// 默認(rèn):將 db 的輸出(主要為 SQL 語句)重定向到"黑洞"(不輸出執(zhí)行的 SQL 語句等信息)
db.Stdout = ioutil.Discard
// 默認(rèn):將 db 產(chǎn)生的輸出(主要為 SQL 語句)輸出到標(biāo)準(zhǔn)輸出(控制臺)
db.Stdout = os.Stdout
兼容原生的方法
有時候我們需要調(diào)用原生的接口,來實(shí)現(xiàn)比較復(fù)雜的目的。
// 自定義復(fù)雜 SQL 獲取單條結(jié)果(原生)
var uid int64
err = db.QueryRow("SELECT uid FROM user WHERE uid=?", 2).Scan(&uid)
if err != nil {
panic(err)
}
fmt.Printf("uid: %v\n", uid)
db.Table("user").LoadCache() // 自定義需要手動刷新緩存
用例
package main
import (
"github.com/mydeeplike/dbx"
"fmt"
"os"
"time"
)
type User struct {
Uid int64 `db:"uid"`
Gid int64 `db:"gid"`
Name string `db:"name"`
CreateDate time.Time `db:"createDate"`
}
func main() {
var err error
var db *dbx.DB
// db, err = dbx.Open("sqlite3", "./db1.db?cache=shared&mode=rwc&parseTime=true&charset=utf8") // sqlite3
db, err = dbx.Open("mysql", "root@tcp(localhost)/test?parseTime=true&charset=utf8") // mysql
dbx.Check(err)
defer db.Close()
// db 輸出信息設(shè)置
db.Stdout = os.Stdout // 將 db 產(chǎn)生的信息(大部分為 sql 語句)輸出到標(biāo)準(zhǔn)輸出
db.Stderr = dbx.OpenFile("./db_error.log") // 將 db 產(chǎn)生的錯誤信息輸出到指定的文件
// db.Stdout = ioutil.Discard // 默認(rèn):將 db 的輸出信息重定向到"黑洞"(不輸出執(zhí)行的 SQL 語句等信息)
// 參數(shù)設(shè)置
db.SetMaxIdleConns(10)
db.SetMaxOpenConns(10)
// db.SetConnMaxLifetime(time.Second * 5)
// 創(chuàng)建表
_, err = db.Exec(`DROP TABLE IF EXISTS user;`)
_, err = db.Exec(`CREATE TABLE user(
uid INT(11) PRIMARY KEY AUTO_INCREMENT,
gid INT(11) NOT NULL DEFAULT '0',
name TEXT DEFAULT '',
createDate DATETIME DEFAULT CURRENT_TIMESTAMP
);
`)
dbx.Check(err)
// 開啟緩存,可選項(xiàng),一般只針對小表開啟緩存,超過 10w 行,不建議開啟!
db.Bind("user2", &User{}, true)
db.EnableCache(true)
// 插入一條
u1 := &User{1, 1, "jack", time.Now()}
_, err = db.Table("user").Insert(u1)
dbx.Check(err)
// 讀取一條
u2 := &User{}
err = db.Table("user").WherePK(1).One(u2)
dbx.Check(err)
fmt.Printf("%+v\n", u2)
// 讀取一條,判斷是否存在
err = db.Table("user").WherePK(1).One(u2)
dbx.Check(err)
if dbx.NoRows(err) {
panic("not found.")
}
fmt.Printf("%+v\n", u2)
// 更新一條
u2.Name = "jack.ma"
_, err = db.Table("user").Update(u2)
dbx.Check(err)
// 刪除一條
_, err = db.Table("user").WherePK(1).Delete()
dbx.Check(err)
// Where 條件 + 更新
_, err = db.Table("user").WhereM(dbx.M{{"uid", 1}, {"gid", 1}}).UpdateM(dbx.M{{"Name", "jet.li"}})
dbx.Check(err)
// 插入多條
for i := int64(0); i < 5; i++ {
u := &User{
Uid: i,
Gid: i,
Name: fmt.Sprintf("name-%v", i),
CreateDate: time.Now(),
}
_, err := db.Table("user").Insert(u)
if err != nil {
panic(err)
}
}
// 獲取多條
userList := []*User{}
err = db.Table("user").Where("uid>?", 1).All(&userList)
dbx.Check(err)
for _, u := range userList {
fmt.Printf("%+v\n", u)
}
// 批量更新
_, err = db.Table("user").Where("uid>?", 3).UpdateM(dbx.M{{"gid", 10}})
dbx.Check(err)
// 批量刪除
_, err = db.Table("user").Where("uid>?", 3).Delete()
if err != nil {
panic(err)
}
// 總數(shù)
n, err := db.Table("user").Where("uid>?", -1).Count()
dbx.Check(err)
fmt.Printf("count: %v\n", n)
// 求和
n, err = db.Table("user").Where("uid>?", -1).Sum("uid")
dbx.Check(err)
fmt.Printf("sum(uid): %v\n", n)
// 求最大值
n, err = db.Table("user").Where("uid>?", -1).Max("uid")
dbx.Check(err)
fmt.Printf("max(uid): %v\n", n)
// 求最小值
n, err = db.Table("user").Where("uid>?", -1).Min("uid")
dbx.Check(err)
fmt.Printf("min(uid): %v\n", n)
// 自定義復(fù)雜 SQL 獲取單條結(jié)果(原生)
var uid int64
err = db.QueryRow("SELECT uid FROM user WHERE uid=?", 2).Scan(&uid)
dbx.Check(err)
fmt.Printf("uid: %v\n", uid)
db.Table("user").LoadCache() // 自定義需要手動刷新緩存
// 自定義復(fù)雜 SQL 獲取多條(原生)
var name string
rows, err := db.Query("SELECT `uid`, `name` FROM `user` WHERE 1 ORDER BY uid DESC")
dbx.Check(err)
rows.Close()
for rows.Next() {
rows.Scan(&uid, &name)
fmt.Printf("uid: %v, name: %v\n", uid, name)
}
db.Table("user").LoadCache() // 自定義需要手動刷新緩存
return
}
