草庐IT

【MySQL】MySQL建表与常见类型设计陷阱(MySQL专栏启动)

小明java问道之路 2023-08-25 原文

📫作者简介:小明java问道之路,专注于研究 Java/ Liunx内核/ C++及汇编/计算机底层原理/源码,就职于大型金融公司后端高级工程师,擅长交易领域的高安全/可用/并发/性能的架构设计与演进、系统优化与稳定性建设。

 

📫 热衷分享,喜欢原创~ 关注我会给你带来一些不一样的认知和成长。

 

🏆 InfoQ签约作者、CSDN专家博主/后端领域优质创作者/内容合伙人、阿里云专家/签约博主、51CTO专家 🏆

 

🔥如果此文还不错的话,还请👍关注、点赞、收藏三连支持👍一下博主~ 

本文目录​​​​​​​

本文导读

一、MySQL建表语句

二、MySQL建表字符串类型设计

1、CHAR

2、VARCHAR

3、枚举类型设计实战

三、MySQL建表ID和金额的设计与实战

1、ID自增的设计

2、互联网企业金额字段设计原理

四、MySQL建表时间类型设计与实战

五、MySQL高扩展JSON设计与实战

总结


本文导读

本文作为MySQL系列第三篇文章,详细讲解了MySQL的建表语句、以及表结构的设计规范和陷阱,对网络上常见的资料给出的设计方案,做了博主自己的理解和反驳。

一、MySQL建表语句

MySQL建表语句很简单,CREATE TABLE `表名` (),在其中设置表的列(属性)即可。

CREATE TABLE `表名`  (
    // 定义属性
    // 定义索引
) // 设置表属性;

二、MySQL建表字符串类型设计

MySQL 数据库的字符串类型有 CHAR、VARCHAR、BINARY、BLOB、TEXT、ENUM、SET,其中最常使用的是 CHAR、VARCHAR

1、CHAR

CHAR(N) 用来保存固定长度的字符(在Unicode字符集,Utf-8、Utf-16、Utf-32是这样的),N 的范围是 0 ~ 255,请牢记,N 表示的是字符,而不是字节。

在表结构设计中还需要额外定义建表对应的字符集。多字节字符集 (MBCS),通常指的是ANSI、中文编码以及Shift-jis,jis,euc-jp,euc-kr等。Unicode字符集,Unicode字符集即平常说的宽字节,包含Utf-8、Utf-16、Utf-32。

常见的字符集有 GBK、UTF8,通常推荐把默认字符集设置为 UTF8。

2、VARCHAR

VARCHAR(N) 用来保存变长字符,N 的范围为 0 ~ 65536, N 表示字符。在超出 65536 个字符的情况下,可以考虑使用更大的字符类型 TEXT 或 BLOB,两者最大存储长度为 4G,其区别是 BLOB 没有字符集属性,纯属二进制存储。

随着移动互联网的飞速发展,推荐把 MySQL 的默认字符集设置为 UTF8MB4,否则,某些 emoji 表情字符无法在 UTF8 字符集下存储。

MySQL 8.0 版本字符集默认设置成 UTF8MB4,UTF8MB4 字符集 1 个字符最大存储 4 个字节,8.0 版本之前默认的字符集为Latin1。

鉴于目前默认字符集推荐设置为 UTF8MB4,所以在表结构设计时,可以把 CHAR 全部用 VARCHAR 替换,底层存储的本质实现一模一样。

3、枚举类型设计实战

枚举类型设计

设计表结构时,你会遇到一些固定选项值的字段。例如状态字段(***_state),有效的值为有限状态,例如01(订单初始状态)、02(下单成功)、03(支付中)……。

很多学习资料和博客推荐在 MySQL 8.0 版本之前,可以使用 ENUM 字符串枚举类型,只允许有限的定义值插入。如果将参数 SQL_MODE 设置为严格模式,插入非定义数据就会报错。

这里博主要跟这些资料唱个反调,我们在工程中的状态,基本都是我们手动set的,这里博主认为如果使用了 ENUM 字符串枚举类型恰恰不利于互联网的高速扩展的设计原则。

在这里我推荐在工程中维护一个 ENUM 枚举类,我们对数据库操作的的时候状态或者相关枚举类型的字段从枚举类中获取,这样方便维护,并且利于扩展。

`TXN_TYPE` varchar(8) CHARACTER  NOT NULL COMMENT '交易类型|消费:SQT,退货:SQRT',

三、MySQL建表ID和金额的设计与实战

1、ID自增的设计

进行实战设计之前,我们需要了解整型类型,

MySQL 数据库支持 SQL 标准支持的整型类型:INT、SMALLINT、TINYINT、MEDIUMINT 和 BIGINT 整型类型。INT占用4字节,取值范围是-2147483648 ~ 2147483647(2^31),BIGINT占用8字节,-9223372036854775808 ~9223372036854775807(2^63)

除了整型类型,数字类型还有浮点和高精度类型。MySQL 之前的版本中存在浮点类型 Float 和 Double,在真实的生产环境中不推荐使用,在计算时由于精度类型问题,会导致最终的计算结果出错。

ID一般我们会设置为自增,结合 auto_increment,可以实现自增功能,但在表结构设计时用自增做主键一般只会使用 BIGINT 类型做主键。

`ID` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id',

原因有几点一是为了扩展性,int 的取值范围不一定适用互联网场景的增速,这里面需要注意MySQL 8.0 版本前自增不持久化,自增值可能会存在回溯问题,例如 1/2/3/4,我把4删点,再次插入的时候,主键ID还是 1/2/3/4,这就是回溯问题,解决办法就是在使用的时候评估这个方案会不会有影响,或者直接升级MySQL。

2、互联网企业金额字段设计原理

我们常常在其他博客看到这样一种说法“在海量互联网业务的设计标准中,并不推荐用 DECIMAL 类型,而是更推荐将 DECIMAL 转化为 整型 BIGINT类型。”,他给出的理由是所有金额相关字段都是定长字段,占用 8 个字节,存储高效。第二直接通过整型计算,效率更高。

而事实上真的是这样吗?

金额字段的取值范围如果用 DECIMAL 表示的,则定义为 DECIMAL(16,2) ,这样满足的万亿以上的场景了。

`TRANS_AMT` decimal(16, 0) NULL DEFAULT NULL COMMENT '子单交易金额',
`CASH_AMT` decimal(16, 0) NULL DEFAULT NULL COMMENT '子单现金金额',
`POINT_AMT` decimal(16, 0) NULL DEFAULT NULL COMMENT '子单积分金额',

为什么我推荐使用DECIMAL而不是BIGINT,我们在存储金额的时候一般是分为单位,例如100,.00就是 1 元,当我们下单金额例如100元,我们的库里就会落 10000.00,但是这比订单购买了1个item商品3件sku,这100元就要分摊给这3件sku商品,这时候对于分摊的计算,在代码中int、long类型没有BigDecimal 计算的精准。

四、MySQL建表时间类型设计与实战

MySQL 数据库中常见的日期类型有 YEAR、DATE、TIME、DATETIME、TIMESTAMEP。

因为业务绝大部分场景都需要将日期精确到秒,所以在表结构设计中,常见使用的日期类型为DATETIME 和 TIMESTAMP。

这里面TIMESTAMP有一个大坑,TIMESTAMP 其实际存储的内容为‘1970-01-01 00:00:00’到现在的毫秒数。在 MySQL 中,由于类型 TIMESTAMP 占用 4 个字节,因此其存储的时间上限只能到‘2038-01-19 03:14:07’。

我们工程中,生产环境等等一般使用的是DATETIME, DATETIME 最终展现的形式为:YYYY-MM-DD HH:MM:SS,固定占用 8 个字节。

从 MySQL 5.6 版本开始,DATETIME 类型支持毫秒,DATETIME(N) 中的 N 表示毫秒的精度。例如,DATETIME(6) 表示可以存储 6 位的毫秒值。同时,DATETIME 不存在时区转化问题。一般是在国际化项目中,服务器端或者前端进行转换,这样查询或者变更效率更高。

每个表都要有一个时间字段,在做表结构设计规范时,强烈建议你每张业务核心表都增加一个 DATETIME 类型的 last_modify_date 字段,并设置修改自动更新机制, 即便标识每条记录最后修改的时间。开发人员可以知道每次操作记录更新的时间,以便做后续的处理。

`CREATE_TIME` datetime(0) NOT NULL COMMENT '创建时间',
`CREATE_BY` varchar(32) NOT NULL COMMENT ' 创建人',
`UPDATE_TIME` datetime(0) NOT NULL DEFAULT CURRENT_TIMESTAMP(0) ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
`UPDATE_BY` varchar(32) CHARACTER NOT NULL COMMENT '更新人',

比如在电商的订单表中,可以方便对支付超时的订单做处理;在金融业务中,可以根据用户资金最后的修改时间做相应的资金轧差等。

五、MySQL高扩展JSON设计与实战

关系型的结构化存储存在一定的弊端,因为它需要预先定义好所有的列以及列对应的类型。但是业务在发展过程中,或许需要扩展单个列的描述功能。

这时,如果能用好 JSON 数据类型,那就能打通关系型和非关系型数据的存储之间的界限,为业务提供更好的架构选择。JSON 类型的另一个好处是无须预定义字段,字段可以无限扩展。

`ITEM_INFO` JSON COMMENT '商品信息',

但是这里,博主并不推荐大家这么做,因为JSON类型及其难维护,并且写sql的时候很麻烦

我举个例子,我想插入一条信息,我需要

SET @item_info = '{
	"item_id" : "12345",
	"item_amt" : "1024.00"
}';

INSERT INTO 表名 VALUES ( , @item_info);

一般在生产中我们这样处理,在定义时,定义一个超大的字符串类型,在代码中使用JSON转换成一个JSON对象的字符串,保存。

`ITEM_INFO` varchar(1000) CHARACTER DEFAULT NULL COMMENT '商品信息',

总结

本文作为MySQL系列第三篇文章,详细讲解了MySQL的建表语句、以及表结构的设计规范和陷阱,对网络上常见的资料给出的设计方案,做了博主自己的理解和反驳。

有关【MySQL】MySQL建表与常见类型设计陷阱(MySQL专栏启动)的更多相关文章

  1. ruby-on-rails - Rails - 子类化模型的设计模式是什么? - 2

    我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co

  2. ruby - Infinity 和 NaN 的类型是什么? - 2

    我可以得到Infinity和NaNn=9.0/0#=>Infinityn.class#=>Floatm=0/0.0#=>NaNm.class#=>Float但是当我想直接访问Infinity或NaN时:Infinity#=>uninitializedconstantInfinity(NameError)NaN#=>uninitializedconstantNaN(NameError)什么是Infinity和NaN?它们是对象、关键字还是其他东西? 最佳答案 您看到打印为Infinity和NaN的只是Float类的两个特殊实例的字符串

  3. ruby - 检查方法参数的类型 - 2

    我不确定传递给方法的对象的类型是否正确。我可能会将一个字符串传递给一个只能处理整数的函数。某种运行时保证怎么样?我看不到比以下更好的选择:defsomeFixNumMangler(input)raise"wrongtype:integerrequired"unlessinput.class==FixNumother_stuffend有更好的选择吗? 最佳答案 使用Kernel#Integer在使用之前转换输入的方法。当无法以任何合理的方式将输入转换为整数时,它将引发ArgumentError。defmy_method(number)

  4. ruby-on-rails - 使用 rails 4 设计而不更新用户 - 2

    我将应用程序升级到Rails4,一切正常。我可以登录并转到我的编辑页面。也更新了观点。使用标准View时,用户会更新。但是当我添加例如字段:name时,它​​不会在表单中更新。使用devise3.1.1和gem'protected_attributes'我需要在设备或数据库上运行某种更新命令吗?我也搜索过这个地方,找到了许多不同的解决方案,但没有一个会更新我的用户字段。我没有添加任何自定义字段。 最佳答案 如果您想允许额外的参数,您可以在ApplicationController中使用beforefilter,因为Rails4将参数

  5. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  6. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  7. ruby - 查找字符串中的内容类型(数字、日期、时间、字符串等) - 2

    我正在尝试解析一个CSV文件并使用SQL命令自动为其创建一个表。CSV中的第一行给出了列标题。但我需要推断每个列的类型。Ruby中是否有任何函数可以找到每个字段中内容的类型。例如,CSV行:"12012","Test","1233.22","12:21:22","10/10/2009"应该产生像这样的类型['integer','string','float','time','date']谢谢! 最佳答案 require'time'defto_something(str)if(num=Integer(str)rescueFloat(s

  8. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  9. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  10. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

随机推荐