乐趣区

关于golang:Mysql对数据库设计时设计标识字段引发的一些思考

前言

哈喽,everybody,我是 asong。明天 asong 教你们一个 mysql 优化设计状态标识。学会了他,咱们的 DB 构造看起来更清晰,也防止了 DB 构造过大的问题,具体怎么设计,上面你就看我怎么操作就好了~~~

背景

咱们在很多利用场景中,通常是须要给数据加上一些标识,已表明这条数据的某个个性。比方标识用户的领取渠道,标识商家的结算形式、商品的类型等等。对于这样的具备无限固定的几个值的标识,咱们通过枚举的形式来标识就能够了,然而对于一些同时具备多个属性且变动比拟大的就显然不适合了,举个很简略的例子,咱们在某宝上想买一个平板,这个平板的商品类型可标识为电子商品、二手商品、、手机、数码等等,对于这种场景,一个商品对应多种类型,不确定性很大,这种就不是简略的通过几个值标识就能解决的了。本文就是针对这个问题,给出了本人的一些思考。

问题与剖析

咱们就拿最近刚过去的双 11 举个例子,在双 11 要开始之前,某宝就会通过各种优惠的形式发放优惠卷、积分抵扣等等福利,这样咱们在双 11 清空购物车时享受这些优惠。这种场景其实对咱们程序员来说并不是简略的实现优惠减免这么简略,这种场景更多是标识优惠以计算用户理论所需领取金额,以及为后续业绩统计、制订促销打算、进步用户活跃度等提供数据根据。上面咱们依据例子进行剖析:

假如以后某宝平台能够应用的优惠形式如下:

序号 优惠内容 应用条件 是否长期有效 备注
1 账户余额 间接抵扣现金 用户充值取得(平台处分吸引的充值,如:充 100 送 10 元)
2 平台积分 100 积分抵扣 1 元 通过参加平台流动、购物行为积攒获取
3 满减卷 5 元 满 100 减 5 元 平台流动促销发放
4 免邮费 订单总金额符合条件即可 平台单笔订单总金额满 199 元免邮费

当用户进行下单时,只有满足各优惠的应用条件时,就能够应用各种优惠。这时咱们思考一个问题,数据库是怎么存储这些优惠的呢?

依据下面的举例,用户下单时能够同时应用下面 4 种优惠抵扣形式,也就说用户可能呈现的组合有 2^4 - 1=15 种,如果咱们的表结构设计成独自用一个一般标识字段来标识存储,实现起来是比较简单,然而其须要标识的组合品种切实有点多,不太利于编码与后续扩大,想一想,优惠政策会随着平台倒退一直推出的,如果新加了一种优惠类型,其须要增加多少种组合标识啊,且呈指数式爆长,这种形式显然不太正当。那么有没有什么解决方案呢?

计划一:

采纳另外引入一张关联表的形式,专门用一张关联表来存储订单应用的优惠组合信息,每应用一种优惠就增加一条关联记录,相比独自应用一般字段标识,这在肯定水平上缩小了设置标识的繁琐性,减少了灵活性(每多应用一种优惠就增加一条关联记录),然而,同时也带来了另一些问题,其中次要问题是:新增一张关联表后,数据保护起来麻烦。在互联网场景下,数据量通常是十分大的,像订单数据个别都须要进行数据库 sharding,以应答数据量暴涨后数据库的读写性能瓶颈,减少零碎的程度扩大能力。因而,另外减少一张数据量是订单数据自身数据量几倍的关联表也显然不太适合。

计划二:

这就是本文的重点了,也就是咱们应用“非凡标识位”的形式来实现,具体思路如下:

  • 咱们不再间接应用十进制数字来标识存储优惠信息,而是存储一个二进制数转化后的十进制数,这些 1、2、3 之类的优惠数字示意占二进制数的第几位(从右至左数);
  • 具体数据的存储、读取判断通过一个通用办法进行转换。

当初咱们假如应用 int32 数据类型进行存储,共 32 位,除去符号位,可用于标识的位数有 31 位,即最多能够标识 31 种优惠状况。

优惠项 占第几位 二进制数 十进制数
账户余额 1 0000 0001 1
平台积分 2 0000 0010 2
满减卷 5 元 3 0000 0100 4
免邮费 4 0000 1000 8

阐明:若用户应用了账户余额, 则应用二进制数 00000001 标识,若应用了平台积分,则应用二进制数 00000010 标识,存储到 DB 时,转换成对应十进制数别离对应 1、2;若同时应用了账户余额、平台积分,则应用二进制数 00000011 标识,最终存储到 DB 的对应十进制数是 3。其它优惠项,所占的二进制位顺次类推。

代码样例

先看代码

package main

import ("fmt")

// golang 没有 enum 应用 const 代替
const (
    TYPE_BALANCE      = 1 // type = 1
    TYPE_INTEGRAL     = 2 // type = 2
    TYPE_COUPON       = 3 // type = 3
    TYPE_FREEPOSTAGE  = 4 // type = 4
)

// 是否应用有优惠卷
func IsUseDiscount(discountType , value uint32) bool {return (value & (1<< (discountType-1))) > 0
}


// 设置应用
func SetDiscountValue(discountType ,value uint32) uint32{return value | (1 << (discountType-1))
}

func main()  {
    // 测试 1 不设置优惠类型
    var flag1 uint32 = 0
    fmt.Println(IsUseDiscount(TYPE_BALANCE,flag1))
    fmt.Println(IsUseDiscount(TYPE_INTEGRAL,flag1))
    fmt.Println(IsUseDiscount(TYPE_COUPON,flag1))
    fmt.Println(IsUseDiscount(TYPE_FREEPOSTAGE,flag1))


    // 测试 2 只设置一个优惠类型
    var flag2 uint32 = 0
    flag2 = SetDiscountValue(TYPE_BALANCE,flag2)
    fmt.Println(IsUseDiscount(TYPE_BALANCE,flag2))
    fmt.Println(IsUseDiscount(TYPE_INTEGRAL,flag2))
    fmt.Println(IsUseDiscount(TYPE_COUPON,flag2))
    fmt.Println(IsUseDiscount(TYPE_FREEPOSTAGE,flag2))

    // 测试 3 设置两个优惠类型
    var flag3 uint32 = 0
    flag3 = SetDiscountValue(TYPE_BALANCE,flag3)
    flag3 = SetDiscountValue(TYPE_INTEGRAL,flag3)
    fmt.Println(IsUseDiscount(TYPE_BALANCE,flag3))
    fmt.Println(IsUseDiscount(TYPE_INTEGRAL,flag3))
    fmt.Println(IsUseDiscount(TYPE_COUPON,flag3))
    fmt.Println(IsUseDiscount(TYPE_FREEPOSTAGE,flag3))

    // 测试 4 设置三个优惠类型
    var flag4 uint32 = 0
    flag4 = SetDiscountValue(TYPE_BALANCE,flag4)
    flag4 = SetDiscountValue(TYPE_INTEGRAL,flag4)
    flag4 = SetDiscountValue(TYPE_COUPON,flag4)
    fmt.Println(IsUseDiscount(TYPE_BALANCE,flag4))
    fmt.Println(IsUseDiscount(TYPE_INTEGRAL,flag4))
    fmt.Println(IsUseDiscount(TYPE_COUPON,flag4))
    fmt.Println(IsUseDiscount(TYPE_FREEPOSTAGE,flag4))

    // 测试 5 设置四个优惠类型
    var flag5 uint32 = 0
    flag5 = SetDiscountValue(TYPE_BALANCE,flag5)
    flag5 = SetDiscountValue(TYPE_INTEGRAL,flag5)
    flag5 = SetDiscountValue(TYPE_COUPON,flag5)
    flag5 = SetDiscountValue(TYPE_FREEPOSTAGE,flag5)
    fmt.Println(IsUseDiscount(TYPE_BALANCE,flag5))
    fmt.Println(IsUseDiscount(TYPE_INTEGRAL,flag5))
    fmt.Println(IsUseDiscount(TYPE_COUPON,flag5))
    fmt.Println(IsUseDiscount(TYPE_FREEPOSTAGE,flag5))
}

运行后果:

false
false
false
false
true
false
false
false
true
true
false
false
true
true
true
false
true
true
true
true

因为 go 没有枚举,所以咱们应用 const 申明常量的形式来实现,定义四个常量,代表四种优惠品种,这个并不是最最终存储到 DB 的值,而是示意占二进制数的第几位(从右至左数,从 1 开始);当须要存储优惠品种到 DB 中,或者从 DB 中查问对应的优惠品种时,通过 SetDiscountValueIsUseDiscount这两个办法对值进行设置(我的项目中能够封装一个文件中作为工具类)。

SetDiscountValue办法的实现:通过位运算来实现,(1 << (discountType-1))通过位移的办法来找到其在二进制中的地位,而后通过与 value 位或的办法设定所占二进制位数,最终返回设置占位后的十进制数。

IsUseDiscount办法的实现:(1<< (discountType-1))通过位移的办法来找到其在二进制中的地位,而后通过与 value 位与的办法来判断优惠项应占位是否有占位,返回判断后果。

下面就是一个应用 非凡标识位 的一个简略代码样例,这个程序还能够进行扩大与欠缺,期待你们的开发呦~~~。

总结

在这里简略总结一下应用非凡标识位的优缺点:

  • 长处

    • 不便扩大,易于保护;当业务场景迅速扩大时,这种形式能够不便的标识新增的业务场景,数据也易于保护。要晓得,在互联网场景下,业务的变动是十分快的,新加字段并不是那么不便。
    • 不便标识存储,一个字段就能够标识多种业务场景。
  • 毛病

    • 数据的存储、查问须要转换,不够直观;绝对一般的标识形式,没接触过的人须要一点工夫了解这种应用非凡标识位的形式。
    • DB 数据查问时,稍显繁琐。

你们学废了嘛?反正我学废了,哈哈哈哈哈~~~~~。

好啦,这一篇文章到这就完结了,咱们下期见~~。心愿对你们有用,又不对的中央欢送指出,可增加我的 golang 交换群,咱们一起学习交换。

结尾给大家发一个小福利吧,最近我在看 [微服务架构设计模式] 这一本书,讲的很好,本人也收集了一本 PDF,有须要的小伙能够到自行下载。获取形式:关注公众号:[Golang 梦工厂],后盾回复:[微服务],即可获取。

我翻译了一份 GIN 中文文档,会定期进行保护,有须要的小伙伴后盾回复 [gin] 即可下载。

翻译了一份 Machinery 中文文档,会定期进行保护,有须要的小伙伴们后盾回复 [machinery] 即可获取。

我是 asong,一名普普通通的程序猿,让 gi 我一起缓缓变强吧。我本人建了一个 golang 交换群,有须要的小伙伴加我vx, 我拉你入群。欢送各位的关注,咱们下期见~~~

举荐往期文章:

  • machinery-go 异步工作队列
  • go 参数传递类型
  • 手把手教姐姐写音讯队列
  • 常见面试题之缓存雪崩、缓存穿透、缓存击穿
  • 详解 Context 包,看这一篇就够了!!!
  • go-ElasticSearch 入门看这一篇就够了(一)
  • 面试官:go 中 for-range 应用过吗?这几个问题你能解释一下起因吗
退出移动版