• 【go-postgres】在go中,实现自动分表


    前言

    postgres在数据量达到500万左右单表,查询速度就会明显变慢。当数据具备明显分区界限,并且其数据总量大于500万时,那么这样的数据,最好分表存放。

    分表存放的数据具备以下特点:

    • 不同区的数据,无查询依赖。只存在单区实时查询。
    • 单区数据量有上限,控制在百万级。
    • 无法预估未来会出现几个区,也就是无法预估上限。

    本次方案中的分表实现,是在go中通过代码实现分区的,原因有:

    • 数据库属于运维职能,业务分表属于业务开发职能。分表由开发做,可操作性更丰富,和运维组交付存在多余的提单成本。
    • 分表使用通用的orm框架分表,这样的框架会同时支持mysql,oracal等常见关系型数据库,也就是同一份分表逻辑,能适配到不同的数据库里,合理应对跨库,移库等操作,省去分表策略在跨库的方案调用和调整。

    实现

    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
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 注意,代码中省略了config.Appname和config.Mode 表示应用名(xxxsrv)和模式(pro,dev,local)

    使用

    以下用例,表示了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)
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
  • 相关阅读:
    微软(TTS)文本转语音服务API实现
    计算机视觉与深度学习-卷积神经网络-卷积&图像去噪&边缘提取-图像去噪 [北邮鲁鹏]
    汽车虚拟仿真技术的实现、应用和未来
    2022/08/19、20 day06/07:Linux的redis安装
    华为OD机试(含B卷)真题2023 算法分类版,58道20个算法分类,如果距离机考时间不多了,就看这个吧,稳稳的
    Java Final关键字使用
    ITSource 分享 第3期【在线个人网盘】
    实战项目:负载均衡式在线OJ
    超前预告 | 云原生?大模型?这届乌镇双态IT大会亮点有点多
    python爬虫实战之逆向分析酷狗音乐
  • 原文地址:https://blog.csdn.net/fwhezfwhez/article/details/126241320