postgres在数据量达到500万左右单表,查询速度就会明显变慢。当数据具备明显分区界限,并且其数据总量大于500万时,那么这样的数据,最好分表存放。
分表存放的数据具备以下特点:
本次方案中的分表实现,是在go中通过代码实现分区的,原因有:
package db
import (
"fmt"
"github.com/fwhezfwhez/cmap"
"github.com/fwhezfwhez/errorx"
"github.com/garyburd/redigo/redis"
"github.com/jinzhu/gorm"
"strings"
"time"
)
var existTable = cmap.NewMapV2(nil, 2, 15*time.Second)
type table interface {
TableName() string // 分表后的表名
SourceTableName() string // 母表表名
}
var tmpl = `create table ${table_name} (like ${source_table_name} including all);`
func MustHaveTable(
engine *gorm.DB, // 提供数据查表是否创建,以及创表句柄
t table, // 提供表的源表名,和分表名
f func() redis.Conn, // 提供获取conn的方法,用于分布式环境创建唯一)
) (bool, error) {
tablename := t.TableName()
created, exist := existTable.Get(tablename)
if exist && created == true {
return true, nil
}
if engine.HasTable(t) {
existTable.SetEx(tablename, true, 60)
return true, nil
}
// 15秒内的并发下,只会有一条,走进创建语句
conn := f()
defer conn.Close()
if !once(conn, fmt.Sprintf("%s:auto_create_%s:%s", config.Node.AppName, config.Node.Mode, t.TableName()), 15) {
return true, nil
}
sql := strings.ReplaceAll(tmpl, "${table_name}", t.TableName())
sql = strings.ReplaceAll(sql, "${source_table_name}", t.SourceTableName())
var seqName = fmt.Sprintf("%s_id_seq", tablename)
// do create sequence
if e := engine.Exec(strings.ReplaceAll("create sequence ${seq_name} start with 1 increment by 1 no minvalue no maxvalue cache 1", "${seq_name}", seqName)).Error; e != nil {
return false, errorx.Wrap(e)
}
// do create table
if e := engine.Exec(sql).Error; e != nil {
return false, errorx.Wrap(e)
}
if e := engine.Exec(strings.ReplaceAll(strings.ReplaceAll("alter table ${table_name} alter column id set default nextval('${seq_name}')", "${table_name}", tablename), "${seq_name}", seqName)).Error; e != nil {
return false, errorx.Wrap(e)
}
existTable.SetEx(t.TableName(), true, 60)
return true, nil
}
func once(conn redis.Conn, key string, seconds int) bool {
rs, e := redis.String(conn.Do("set", key, "done", "ex", seconds, "nx"))
if e == redis.ErrNil {
return false
}
if rs == "OK" {
return true
}
return false
}
以下用例,表示了UserData数据,按照game_id分表
type UserData struct {
Id int `gorm:"column:id;default:" json:"id" form:"id"`
GameId int `gorm:"column:game_id;default:" json:"game_id" form:"game_id"`
CreatedAt time.Time `gorm:"column:created_at;default:" json:"created_at" form:"created_at"`
Cards json.RawMessage `gorm:"column:cards;default:" json:"cards" form:"cards"`
}
func (o CardHeap) TableName() string {
return fmt.Sprintf("user_data_%d", o.GameId)
}
func (o CardHeap) SourceTableName() string {
return "user_data"
}
func main() {
var ud = UserData{
GameId: 9,
}
_, e := db.MustHaveTable(engine, chg, func() redis.Conn {
return redistool.RedisPool.Get()
})
if e != nil {
return 0, errorx.Wrap(e)
}
}