..

Gorm 分表中间件(Sharding)

Sharding 是一个高性能的 Gorm 分表中间件。它基于 Conn 层做 SQL 拦截、AST 解析、分表路由、自增主键填充,带来的额外开销极小。对开发者友好、透明,使用上与普通 SQL、Gorm 查询无差别,只需要额外注意一下分表键条件。 为您提供高性能的数据库访问

一、功能特点

  • 非侵入式设计, 加载插件,指定配置,既可实现分表。
  • 轻快, 非基于网络层的中间件,像 Go 一样快
  • 支持多种数据库。 PostgreSQL 已通过测试,MySQL 和 SQLite 也在路上。
  • 多种主键生成方式支持(Snowflake, PostgreSQL Sequence, 以及自定义支持)Snowflake 支持从主键中确定分表键。

二、示例代码

注意: 依然保持原来的方式使用 db 来查询数据库。 你只需要注意在 CURD 动作的时候,明确知道 Sharding Key 对应的分表,查询条件带 Sharding Key,以确保 Sharding 能理解数据需要对应到哪一个子表

  • Mysql

    Gorm Sharding 中内置了 MySQL 序列主键实现,只需配置PrimaryKeyGenerator: sharding.PKMySQLSequence即可使用。

    您不需要手动创建序列,当 MySQL 序列不存在时,Gorm Sharding 会检查并创建。

    这个序列名后面跟着gorm_sharding_${table_name}_id_seq,例如orders表,序列名就是gorm_sharding_orders_id_seq。

    /**
     * @Author: chentong
     * @Date: 2024/08/27 00:09
     */
    
    package main
    
    import (
        "fmt"
        "testing"
    
        "gorm.io/driver/mysql"
        "gorm.io/gorm"
        "gorm.io/sharding"
    )
    
    type Order struct {
        ID        int64 `gorm:"primarykey"`
        UserID    int64
        ProductID int64
    }
    
    func TestSharding_Mysql(t *testing.T) {
        dsn := "root:123456@tcp(localhost:3306)/dev?parseTime=True&timeout=5s"
        db, err := gorm.Open(mysql.Open(dsn))
        if err != nil {
            panic(err)
        }
    
        for i := 0; i < 2; i += 1 {
            table := fmt.Sprintf("orders_%d", i)
            db.Exec(`DROP TABLE IF EXISTS ` + table)
            db.Exec("CREATE TABLE " + table + " ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `user_id` int(11) DEFAULT NULL,  `product_id` int(11) DEFAULT NULL,  PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;")
        }
    
        db.Use(sharding.Register(
            sharding.Config{
                ShardingKey:         "user_id",                // 分片key
                NumberOfShards:      2,                        // 分片数量
                PrimaryKeyGenerator: sharding.PKMySQLSequence, // 主键生成器, 这里使用 mysql
            },
            []string{"orders"}, // 需要分片 tables 
        ))
    
        // this record will insert to orders_02
        err = db.Create(&Order{UserID: 2}).Error
        if err != nil {
            fmt.Println(err)
        }
    
        // this record will insert to orders_03
        err = db.Exec("INSERT INTO orders(user_id) VALUES(?)", int64(3)).Error
        if err != nil {
            fmt.Println(err)
        }
    
        // this will throw ErrMissingShardingKey error
        err = db.Exec("INSERT INTO orders(product_id) VALUES(1)").Error
        fmt.Println(err)
    
        // this will redirect query to orders_02
        var orders []Order
        err = db.Model(&Order{}).Where("user_id", int64(2)).Find(&orders).Error
        if err != nil {
            fmt.Println(err)
        }
        fmt.Printf("%#v\n", orders)
    
        // Raw SQL also supported
        db.Raw("SELECT * FROM orders WHERE user_id = ?", int64(3)).Scan(&orders)
        fmt.Printf("%#v\n", orders)
    
        // this will throw ErrMissingShardingKey error
        err = db.Model(&Order{}).Where("product_id", "1").Find(&orders).Error
        fmt.Println(err)
    
        // Update and Delete are similar to create and query
        err = db.Exec("UPDATE orders SET product_id = ? WHERE user_id = ?", 2, int64(3)).Error
        fmt.Println(err) // nil
        err = db.Exec("DELETE FROM orders WHERE product_id = 3").Error
        fmt.Println(err) // ErrMissingShardingKey
    
        }