8.4 KiB
8.4 KiB
分表查询服务使用指南
概述
ShardingQueryService 是一个通用的分表查询服务,用于简化多分表查询的实现。它封装了 UNION ALL 查询、分页、排序等通用逻辑,让开发者只需要关注业务特定的筛选条件和列定义。
核心优势
- 代码复用:避免为每个分表重复编写 UNION ALL 查询逻辑
- 统一优化:所有分表查询都使用相同的优化策略(表存在性检查、列名明确指定等)
- 易于维护:通用逻辑集中管理,修改一处即可影响所有使用该服务的表
- 类型安全:通过接口和回调函数保证类型安全
使用步骤
1. 定义筛选条件结构体
// 例如:日志表的筛选条件
type LogFilters struct {
UserID uint // 用户ID
Level string // 日志级别
Keyword string // 关键词搜索
StartTime time.Time // 开始时间
EndTime time.Time // 结束时间
OrderBy string // 排序字段(格式:字段:asc/desc)
}
2. 实现 WHERE 条件构建函数
// buildLogWhereClause 构建日志查询的 WHERE 条件
func (s *LogServiceImpl) buildLogWhereClause(filters any) (string, []any) {
logFilters, ok := filters.(LogFilters)
if !ok {
return "", nil
}
var conditions []string
var args []any
// 时间范围
if !logFilters.StartTime.IsZero() {
conditions = append(conditions, "created_at >= ?")
args = append(args, logFilters.StartTime)
}
if !logFilters.EndTime.IsZero() {
conditions = append(conditions, "created_at <= ?")
args = append(args, logFilters.EndTime)
}
// 用户ID筛选
if logFilters.UserID > 0 {
conditions = append(conditions, "user_id = ?")
args = append(args, logFilters.UserID)
}
// 日志级别筛选
if logFilters.Level != "" {
conditions = append(conditions, "level = ?")
args = append(args, logFilters.Level)
}
// 关键词搜索
if logFilters.Keyword != "" {
conditions = append(conditions, "(message LIKE ? OR context LIKE ?)")
keyword := "%" + logFilters.Keyword + "%"
args = append(args, keyword, keyword)
}
return strings.Join(conditions, " AND "), args
}
3. 实现列名获取函数
// getLogTableColumns 获取日志表的所有列名
func (s *LogServiceImpl) getLogTableColumns() string {
// 只包含模型中定义的字段,确保所有分表都有这些字段
columns := []string{
"id",
"user_id",
"level",
"message",
"context",
"created_at",
"updated_at",
"deleted_at",
}
return strings.Join(columns, ", ")
}
4. 初始化分表查询服务
type LogServiceImpl struct {
shardingService ShardingService
shardingQueryService ShardingQueryService
}
func NewLogService() *LogServiceImpl {
service := &LogServiceImpl{
shardingService: NewShardingService(),
}
// 初始化分表查询服务
service.shardingQueryService = NewShardingQueryService(ShardingQueryConfig{
BaseTableName: "logs",
GetColumns: func() string {
return service.getLogTableColumns()
},
BuildWhereClause: func(filters any) (string, []any) {
return service.buildLogWhereClause(filters)
},
GetAllowedOrderFields: func() map[string]bool {
return map[string]bool{
"id": true,
"user_id": true,
"level": true,
"created_at": true,
"updated_at": true,
}
},
DefaultOrderBy: "created_at:desc",
ModuleName: "log",
})
return service
}
5. 使用分表查询服务
// GetLogs 查询日志列表(支持多分表)
func (s *LogServiceImpl) GetLogs(filters LogFilters, page, pageSize int) ([]models.Log, int64, error) {
// 验证时间范围
valid, err := utils.ValidateTimeRange(filters.StartTime, filters.EndTime)
if !valid {
return nil, 0, err
}
// 获取需要查询的所有分表
tableNames := utils.GetShardingTableNames("logs", filters.StartTime, filters.EndTime)
if len(tableNames) == 0 {
return []models.Log{}, 0, nil
}
// 如果只有一个分表,直接查询(使用 ORM)
if len(tableNames) == 1 {
return s.querySingleTable(tableNames[0], filters, page, pageSize)
}
// 多个分表:使用通用分表查询服务
var logs []models.Log
total, err := s.shardingQueryService.QueryMultipleTables(tableNames, filters, page, pageSize, &logs)
if err != nil {
return nil, 0, err
}
return logs, total, nil
}
// GetAllLogsForExport 获取所有日志用于导出
func (s *LogServiceImpl) GetAllLogsForExport(filters LogFilters) ([]models.Log, error) {
// 验证时间范围
valid, err := utils.ValidateTimeRange(filters.StartTime, filters.EndTime)
if !valid {
return nil, err
}
// 获取需要查询的所有分表
tableNames := utils.GetShardingTableNames("logs", filters.StartTime, filters.EndTime)
if len(tableNames) == 0 {
return []models.Log{}, nil
}
// 如果只有一个分表,直接查询
if len(tableNames) == 1 {
query := s.buildShardingQuery(tableNames[0], filters)
orderBy := filters.OrderBy
if orderBy == "" {
orderBy = "created_at:desc"
}
query = s.applyOrderBy(query, orderBy)
var logs []models.Log
if err := query.Find(&logs); err != nil {
return nil, apperrors.ErrQueryFailed.WithError(err)
}
return logs, nil
}
// 多个分表:使用通用分表查询服务
var logs []models.Log
err = s.shardingQueryService.QueryMultipleTablesForExport(tableNames, filters, &logs)
if err != nil {
return nil, err
}
return logs, nil
}
配置说明
ShardingQueryConfig 字段说明
| 字段 | 类型 | 说明 | 必填 |
|---|---|---|---|
BaseTableName |
string |
基础表名,如 "orders" | 是 |
GetColumns |
func() string |
获取表的所有列名(用于 UNION ALL 查询) | 是 |
BuildWhereClause |
func(any) (string, []any) |
构建 WHERE 条件,返回条件字符串和参数列表 | 是 |
GetAllowedOrderFields |
func() map[string]bool |
获取允许排序的字段列表 | 是 |
DefaultOrderBy |
string |
默认排序,格式:字段:方向,如 "created_at:desc" | 否(默认:created_at:desc) |
ModuleName |
string |
模块名称,用于日志记录 | 否(默认:sharding) |
注意事项
- 列名必须明确指定:不要使用
SELECT *,必须明确列出所有列名,确保不同分表的列数一致 - 只包含模型中的字段:列名列表应该只包含模型中定义的字段,避免查询不存在的字段
- WHERE 条件格式:
BuildWhereClause返回的条件字符串不应该包含WHERE关键字,只返回条件部分(如 "user_id = ? AND status = ?") - 时间范围处理:如果筛选条件包含时间范围,应该在调用
QueryMultipleTables之前验证时间范围 - 单表优化:如果只有一个分表,建议直接使用 ORM 查询,而不是使用 UNION ALL(性能更好)
完整示例
参考 app/services/order_service.go 中的实现,这是使用通用分表查询服务的完整示例。
优势对比
使用通用服务前(每个表都需要重复实现)
// 每个表都需要实现这些方法
func (s *OrderServiceImpl) queryMultipleTablesWithUnion(...) { /* 100+ 行代码 */ }
func (s *LogServiceImpl) queryMultipleTablesWithUnion(...) { /* 100+ 行代码 */ }
func (s *PaymentServiceImpl) queryMultipleTablesWithUnion(...) { /* 100+ 行代码 */ }
// ... 每个表都重复
使用通用服务后(只需配置)
// 每个表只需要配置一次
service.shardingQueryService = NewShardingQueryService(ShardingQueryConfig{
// ... 配置
})
// 使用时只需一行代码
total, err := s.shardingQueryService.QueryMultipleTables(tableNames, filters, page, pageSize, &results)
扩展性
如果需要添加新的通用功能(如缓存、性能监控等),只需要在 ShardingQueryService 中修改一次,所有使用该服务的表都会自动获得这些功能。