ShardingSphere:Error 20024 (44000): Sharding value ‘[B@4732d699‘ must implements Comparable

背景

使用ShardingSphere分表组件,遇到了这么一个报错。这个报错的意思是指,分表键的类型没有实现Comparble接口,导致了这个报错。

问题场景描述

以一个具体的查询为例,我的分表规则是基于 order_no 实现的,具体做法是截取 order_no 的前四位,即表名遵循 t_opay_${{order_no.substring(0, 4)}} 的规则。当执行如下查询时:

SELECT id,order_no,tid FROM `t_opay` WHERE order_no in ('20241118339590899049312411542')

在我的程序里,order_no 字段声明的是 string 类型,我使用 Golang 的 Gorm 来连接分表组件并进行数据库操作。然而,ShardingSphereProxy 实际接收到的 order_no 数据类型却是 []byte,而 []byte 类型并未实现 Comparable 接口,这就导致了报错的出现。

数据库表结构

我们对t_opay基于 order_no进行分表,共分为八张表,以年为维度,物理表从t_opay_2023到t_opay_2030。以下是表结构的 SQL 代码:

-- ----------------------------
-- Table structure for t_opay_2022
-- ----------------------------
DROP TABLE IF EXISTS `t_opay_2022`;
CREATE TABLE `t_opay_2022`  (
  `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
  `order_no` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL DEFAULT '',
  `prepay_id` varchar(2000) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '微信统一下单返回(只有两小时的有效期)',
  `tid` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '微信支付订单号',
  `pay_type` tinyint UNSIGNED NOT NULL DEFAULT 1 COMMENT '支付方式(1:微信小程序;2:微信H5支付)',
  `h5_url` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT 'H5调起支付页面路径',
  `price` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '实付款',
  `ptime` int UNSIGNED NOT NULL DEFAULT 0 COMMENT 'prepay_id最后生成时间',
  `pay_time` int UNSIGNED NOT NULL DEFAULT 0 COMMENT '支付时间',
  `payment_plan` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'NO_INST' COMMENT '支付方案: NO_INST:不分期, INST_3:3期分期, INST_6:6期分期, INST_12:12期分期, FREE_3:3期免息, FREE_6:6期免息, FREE_12:12期免息',
  PRIMARY KEY (`id`) USING BTREE,
  UNIQUE INDEX `u_o`(`order_no` ASC) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 14529 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '订单支付流水' ROW_FORMAT = COMPACT;

解决方案

经过不断地尝试和探索,我找到了一种有效的解决方案。那就是不要直接使用 Gorm 拼接查询条件,因为像下面这种方式会导致上述错误:

db.Where("order_no in ?", filter.OrderNos)

通过直接编写原生 SQL 的方式,可以成功解决这个问题,具体代码如下:

db.Where("order_no in ('?')", gorm.Expr(strings.Join(filter.OrderNos, "','")))

完整测试代码

package main

import (
	"context"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

var globalDB *gorm.DB

func InitDb() *gorm.DB {
	log := logger.Default.LogMode(logger.Info)
	// 连接到 MySQL 数据库
	dsn := "root:root@tcp({你的连接信息})/sharding?charset=utf8mb4&parseTime=True&loc=Local"
	db, err := gorm.Open(mysql.New(mysql.Config{
		DSN: dsn,
	}), &gorm.Config{
		Logger: log,
	})
	if err != nil {
		panic("failed to connect database")
	}
	globalDB = db
	return db
}

func main() {
	InitDb()
	orderPayModelInstance := &OrderPayModel{}
	ctx := context.Background()
	filter := &OrderPayListFilter{
		OrderNos: []string{"20241118339590899049312411542", "20250217533640010482822502144", "20250217533640010482822502145"},
	}
	list, _, err := orderPayModelInstance.List(ctx, filter, false, "*")
	if err != nil {
		panic(err)
	}
	if len(list) > 0 {
		for _, v := range list {
			println("order_no:", v.OrderNo)
		}
	}
	// 使用channel,等待外部信号,结束程序
	c := make(chan struct{})
	<-c
}

// 数据表实体
type OrderPayEntity struct {
	Id          int    `gorm:"column:id;type:int unsigned;primary_key;autoIncrement"` //
	OrderNo     string `gorm:"column:order_no;type:varchar(50)"`                      //
	PrepayId    string `gorm:"column:prepay_id;type:varchar(2000)"`                   //微信统一下单返回(只有两小时的有效期)/支付宝统一下单返回
	Tid         string `gorm:"column:tid;type:varchar(50)"`                           //微信支付订单号
	PayType     int    `gorm:"column:pay_type;type:tinyint unsigned"`                 //支付方式(1:微信小程序;2:微信H5支付;3:微信APP支付;4:支付宝APP支付;6:先试后买)
	H5Url       string `gorm:"column:h5_url;type:varchar(500)"`                       //H5调起支付页面路径
	Price       int    `gorm:"column:price;type:int unsigned"`                        //实付款
	Ptime       int64  `gorm:"column:ptime;type:int unsigned"`                        //prepay_id最后生成时间
	PayTime     int64  `gorm:"column:pay_time;type:int unsigned"`                     //支付时间
	PaymentPlan string `gorm:"column:payment_plan;type:varchar(20)"`                  //支付方案: NO_INST:不分期, INST_3:3期分期, INST_6:6期分期, INST_12:12期分期, FREE_3:3期免息, FREE_6:6期免息, FREE_12:12期免息
}

// 函数方法归属体
type OrderPayModel struct {
}

// 表名
func OrderPayTableName() string {
	return "t_opay"
}

// filter 查询条件
type OrderPayListFilter struct {
	OrderNos []string `json:"order_nos" form:"order_nos"` //订单号列表
	OrderBy  string   `json:"order_by" form:"order_by"`   //排序规则
	Page     int      `json:"page" form:"page"`           //当前页码
	PageSize int      `json:"page_size" form:"page_size"` //每页数据条数
}

func (m OrderPayModel) List(ctx context.Context, filter *OrderPayListFilter, withTotal bool, fields string) ([]*OrderPayEntity, int64, error) {

	var total int64
	list := make([]*OrderPayEntity, 0)

	db := globalDB.WithContext(ctx).Table(OrderPayTableName())
	// todo buildQuery
	m.buildQuery(db, filter)
	//db.Where("order_no in ('20241118339590899049312411542')")
	//db.Where(fmt.Sprintf("order_no in ('%s')", strings.Join(filter.OrderNos, "','")))
	//db.Where("order_no in ('?')", gorm.Expr(strings.Join(filter.OrderNos, "','")))
	// 是否查询总数,部分场景下不需要
	if withTotal {
		if err := db.Count(&total).Error; err != nil {
			return list, 0, err
		}
	}
	// 排序
	if filter.OrderBy != "" {
		db.Order(filter.OrderBy)
	}
	// 分页查询
	if filter.Page != 0 && filter.PageSize != 0 {
		page := filter.Page
		pageSize := filter.PageSize
		db = db.Limit(pageSize).Offset((page - 1) * pageSize)
	}
	// 查询数据
	err := db.Select(fields).Find(&list).Error
	if err != nil {
		return list, total, err
	}
	return list, total, err
}

func (m OrderPayModel) buildQuery(db *gorm.DB, filter *OrderPayListFilter) {
	//增加一个判断,避免没有查询条件时全表查询
	var isEmpty bool = true
	if len(filter.OrderNos) > 0 {
		isEmpty = false
		//db.Where("order_no in ?", filter.OrderNos)
        db.Where("order_no in ('?')", gorm.Expr(strings.Join(filter.OrderNos, "','")))
	}

	// 没有查询条件时,不执行查询操作,避免全表扫描
	if isEmpty {
		db.Where("order_no = ?", "-1")
	}
}

待解决的问题

尽管找到了临时的解决方案,但我仍然对使用 Gorm 导致数据类型变化的原因感到困惑。我仔细查看了 Gorm 的源码,却未能找到产生这个问题的逻辑。我猜测这可能与驱动有关,但目前还不能确定。后续有时间,我会继续深入研究,探寻这背后的真正原因。

如果有哪位大牛知道这个背后的原因,帮忙指点一二,不胜感激。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值