浅谈扩展字段设计

简介: 浅谈扩展字段设计

聊一聊扩展字段设计


1. 背景

工作中我们常常有需求需要加字段,如果数据库数据量比较大,新增字段耗时较长,导致性能下降,甚至出现锁表等问题。

添加扩展字段, 常见的做法有,

  • 动态添加字段
  • 添加扩展表
  • json方式存储
  • xml方式存储


这里我们聊聊基于KV行存储和基于按位存储


2.基于KV水平存储

场景:例如现在有张订单表,需要新增field_1,field_2 字段,并且以后可能会无限扩展字段


·kv表结构设计

CREATE TABLE `order_kv` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `order_id` varchar(30) NOT NULL COMMENT '订单编号',
  `key` varchar(30) NOT NULL COMMENT '存储字段名',
  `value` varchar(3000) NOT NULL DEFAULT '' COMMENT '存储value',
  `create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间',
  `type` tinyint(4) NOT NULL DEFAULT '1' COMMENT '字段类型: 1: string , 2: json',
  PRIMARY KEY (`id`),
  UNIQUE KEY `order_id` (`order_id`,`key`),
  KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单扩展字段KV表';


注 :实际场景中会对kv表进行分表/分库, 因为都是id维度,可以按照id 取hash 分表/分库


·代码改造如下

 //1. 从配置中心获取参数key(这里我就直接写死了)
        String fieldKeys = "field_a,field_b";
        List<String> fieldKeyArr = Splitter.on(",").trimResults().omitEmptyStrings().splitToList(fieldKeys);
        //2. 从request中获取需要存储param
        if (CollectionUtils.isEmpty(fieldKeyArr)) {
            return;
        }
        //3. 获取request中的参数
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        for (String key : fieldKeyArr) {
            String value = request.getParameter(key);
            if (StringUtils.isEmpty(value)) {
                continue;
            }
            //4组装扩展字段 
        }
        //5 存储扩展字段


·打通查询接口

查询语句如下:

SELECT  key,value FROM order_KV where order_id = {order_id} and key in('field_a','field_b')

查询接口我们可以新增返回字段Map(String,String) extData,将扩展字段以map的方式返回,以后新增字段就无需改代码了,只需更改配置中心配置即可。


3.基于按位存储设计

适合场景: 我们经常会有新增字段,并且字段类型为布尔(只有0和1)的场景


实现思路: 可以新增一个flag(Long型,64位)字段, 由于二进制位只有0和1,所以我们可以利用这个特性来标识只有0和1场景的字段,最多可以表示64个字段。


实现方式如下

·首先定义一个flag实体,定义标志key 和位置

@Data
public class FlagBO {
    @Attribute(name = "key", required = true)
    private String key;
    @Attribute(name = "position", required = true)
    private Byte position;
    public Long getHexLong() {
        if (position <= 64) {
            return (long) Math.pow(2, position -1);//2的n-1次方 二进制位 01 10 100 1000
        }
        return 0L;
    }
}

·定义字段枚举(这里也可以从配置中心定义)

/**
    <!-- 标志位表 一旦设置 不能更改 --> 
  <!-- 注意需要按照规律设置值固定 设置范围 1<= X <= 64  建议按照顺序设置 --> 
*/
public enum OrderFlagEnum {
    orderFieldBooleanA("orderFieldBooleanA", (byte) 1),
    orderFieldBooleanB("orderFieldBooleanB", (byte) 2);
    private String key;
    private Byte position;
    OrderFlagEnum(String key, Byte position) {
        this.key = key;
        this.position = position;
    }
    //获取flag定义列表
    public static List<FlagBO> getOrderFlagBO() {
        List<FlagBO> flagBOList = new ArrayList<>();
        for (OrderFlagEnum orderFlagEnum : OrderFlagEnum.values()) {
            FlagBO flagBO = new FlagBO();
            flagBO.setKey(orderFlagEnum.key);
            flagBO.setPosition(orderFlagEnum.position);
            flagBOList.add(flagBO);
        }
        return flagBOList;
    }
}

·从请求参数中获取字段并且组装成flag

 /**
     * encode flag
     *
     * @param request
     * @return
     */
    public Long getFlagFromRequest(HttpServletRequest request) {
        long flag = 0;
        //获取flag配置
        List<FlagBO> orderFlags = getOrderFlags();
        if (orderFlags != null && !orderFlags.isEmpty()) {
            for (FlagBO orderFlag : orderFlags) {
                String parameter = request.getParameter(orderFlag.getKey());
                if (StringUtils.isNumeric(parameter)) {
                    if (Integer.valueOf(parameter) == 0) {
                        continue;
                    }
                    flag = flag | orderFlag.getHexLong();
                }
            }
        }
        return flag;
    }

·查询的时候我们只需要将flag反解出来即可

·同样在查询接口可以添加至extData

 /**
     * decode flag
     * @param flag
     */
    public Map<String, Object> decodeOrderFlag(Long flag) {
        Map<String, Object> flagMap = new HashMap<>();
        List<FlagBO> orderFlags = getOrderFlags();
        if (orderFlags != null && !orderFlags.isEmpty()) {
            for (FlagBO orderFlag : orderFlags) {
                if ((flag & orderFlag.getHexLong()) == orderFlag.getHexLong()) {
                    flagMap.put(orderFlag.getKey(), 1);
                } else {
                    flagMap.put(orderFlag.getKey(), 0);
                }
            }
        }
        return flagMap;
    }

·实际运用中我们会遇到这样的场景,比如更新的时候可能同时有添加删除的标志位场景,如何处理?

·我们可以将 添加 和 删除 操作分类为addFlag 和 subFlag

 /**
     * 获取更新标志位
     * addFlag 新增
     * subFlag 删除
     * flagMap 中 value>0 为添加 否则为删除标志位操作
     * @param flagMap
     * @return
     */
    public flagUpdateBO getUpdateFlagBOByMap(Map<String, Integer> flagMap) {
        flagUpdateBO flagUpdateBO = new flagUpdateBO();
        Long addFlag = 0L;
        Long subFlag = 0L;
        Map<String, FlagBO> flagBOMap = getOrderFlagsMap();
        if (flagMap != null && !flagMap.isEmpty()) {
            for (Map.Entry<String, Integer> flagMapEntry : flagMap.entrySet()) {
                //存在
                FlagBO flagBO = flagBOMap.get(flagMapEntry.getKey());
                if (flagBO != null) {
                    if (flagMapEntry.getValue() != null && flagMapEntry.getValue() > 0) {
                        //add flag
                        addFlag = addFlag | flagBO.getHexLong();
                    } else {
                        subFlag = subFlag | flagBO.getHexLong();
                    }
                }
            }
        }
        flagUpdateBO.setAddFlag(addFlag);
        flagUpdateBO.setSubFlag(subFlag);
        return flagUpdateBO;
    }

最后更新的sql为:

update order_info set flag = (flag | #{addFlag}) & (~ #{subFlag})


4.总结

flag和kv对比如下

类型 适合规则 存储类型
flag 只有0和1两种值的情况 0和1
kv 可以任意值 可以存储string 和json


相信以上方案可以解决工作上大部分添加字段的需求,如果还有其它比较好的方式欢迎与我探讨。


相关文章
|
1月前
|
安全 编译器 程序员
C++对C的扩展(下)
C++对C的扩展
29 0
|
3月前
|
编译器 芯片
字扩展与位扩展
字扩展与位扩展
41 0
|
6月前
运放扩展专题
运放扩展是指通过在运放电路中添加一些外部元器件,来扩展运放的功能和性能。运放扩展技术在电子领域中应用广泛,能够实现各种电路的设计和优化。 运放扩展技术的基本原理: 运放扩展技术的基本原理是在运放电路中添加一些外部元器件,改变运放的输入、输出特性,从而实现各种电路的设计和优化。常见的运放扩展技术包括负反馈、正反馈、积分、微分、比较器、滤波等。 负反馈: 负反馈是一种常用的运放扩展技术,它可以改变运放的增益、输入电阻和输出电阻等特性,从而实现电路的设计和优化。负反馈电路一般由运放、反馈电阻和输入电阻组成,通过改变反馈电阻和输入电阻的比例,可以改变电路的增益和输入电阻。 正反馈: 正反馈是一种
18 0
|
7天前
|
存储 自然语言处理
平台设计-代码字段与标签
在平台里描述对象的属性可以使用代码和标签
|
1月前
|
安全 程序员 编译器
C++对C的扩展(上)
C++对C的扩展
31 0
|
2月前
|
存储 编译器 C++
C++新特性 扩展和聚合类型
C++新特性 扩展和聚合类型
|
10月前
|
C语言 C++ 容器
|
存储 C#
扩展按钮图标
扩展按钮图标
310 1
扩展按钮图标
|
存储 SQL JSON
如何不改表结构动态扩展字段?
痛点 软件行业唯一不变的就是变化,比如功能上线之后,客户或 PM 需要对已有的功能增加一些合理的需求,完成这些工作必须通过添加字段解决,或者某些功能的实现需要通过增加字段来降低实现的复杂性等等。
651 0
如何不改表结构动态扩展字段?