聊一聊扩展字段设计

2020-05-06  本文已影响0人  浅谈架构

1. 背景

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

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

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

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

2. 基于KV水平存储

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


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 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个字段。

​ 实现方式如下


@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;

}

}


/**





*/

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 getOrderFlagBO() {

List 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;

}

}


/**

* encode flag

*

* @param request

* @return

*/

public Long getFlagFromRequest(HttpServletRequest request) {

long flag = 0;

//获取flag配置

List 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;

}


/**

* decode flag

* @param flag

*/

public Map decodeOrderFlag(Long flag) {

Map flagMap = new HashMap<>();

List 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 删除

* flagMap 中 value true 为添加 否则为删除

* @param flagMap

* @return

*/

public flagUpdateBO getUpdateFlagBOByMap(Map flagMap) {

flagUpdateBO flagUpdateBO = new flagUpdateBO();

Long addFlag = 0L;

Long subFlag = 0L;

Map flagBOMap = getOrderFlagsMap();

if (flagMap != null && !flagMap.isEmpty()) {

for (Map.Entry 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|

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

上一篇下一篇

猜你喜欢

热点阅读