1.6. 数据类型
MySQL的数据分为以下几个大类:
1. String Types 字符串类型
2. Numeric Types 数字类型
3. Date and Time Types 日期和时间类型
4. Blog Types 存放二进制的数据类型
5. Spatial Types 存放地理数据的类型
1.6.1. 字符串类型
最常用的两个字符串类型:
- CHAR () 固定长度(0-255)的字符串,默认长度是1,如果写入的字符串长度小于指定的长度,多余部分会补空格,读取时会删掉多余空格。
- VARCHAR () 可变长度字符串,按照实际存放的数据计算长度,在存储长度不确定的字符串数据时非常有用,可以节省存储空间。
VARCHAR最前面的1个或2个字节会存储实际字符串内容的长度,值少于 255 个字节,则长度前缀为 1 个字节,否则为 2 个字节。也就是说VARCHAR 最多长度为 65535 个字节。超出部分如果是空格会截断,不是空格会报错。
储存较大文本的两个类型
- MEDIUMTEXT 最大储存16MB(约16百万的英文字符),适合储存JSON对象,CS视图字符串,中短长度的书籍
- LONGTEXT 最大储存4GB,适合储存书籍和以年记的日志。
还有两个用的少一些的
- TINYTEXT 最大储存 255 Bytes
- TEXT 最大储存 64KB,最大储存长度和 VARCHAR 一样,但最好用 VARCHAR,因为 VARCHAR 可以使用索引。
- 所有这些字符串类型都支持国际字符,其中:
- 英文字符占1个字节
- 欧洲和中东语言字符占2个字节
- 像中日这样的亚洲语言的字符占3个字节
所以,如果一列数据的类型为 CHAR(10),MySQL会预留30字节给那一列的值.
1.6.2. 整数类型
MySQL里共有5种常用的整数类型,它们的区别在于占用的空间和能记录的数字范围
整数类型 | 占用储存 | 记录的数字范围 |
TINYINT | 1B | [-128,127] |
SMALLINT | 2B | [-32K,32K] |
MEDIUMINT | 3B | [-8M,8M] |
INT | 4B | [-2B,2B] |
BIGINT | 8B | [-9Z,9Z] |
属性1. 不带符号 UNSIGNED
这些整数可以选择不带符号,加上 UNSIGNED 则只储存非负数。
如最常用的 UNSIGNED TINYINT,占用空间和 TINYINT 一样也是1B,但表示的数字范围不是 [-128-127] 而是 [0-255],适合储存像年龄这样的数据,可以防止意外输入负数。
最佳实践
使用能满足你需求的最小整数类型,如储存人的年龄用 UNSIGNED TINYINT 就足够了,至少可见的未来内没人能活过255岁,因为数据需要在磁盘和内存间传输,虽然不同类型间只有几个字节的差异,但数据量大了以后对空间和查询效率的影响就很大了,所以在数据量较大时,有意识地分配每一字节,保持数据最小化是很有必要的。
1.6.3. 定点数类型和浮点数类型
Fixedpoint Types 定点数类型
DECIMAL(最大数字位数,小数点后位数) ;
DECIMAL(9, 2) => 1234567.89 最多9位,小数点后两位,整数部分最多7位
DECIMAL 还有几个别名:DEC / NUMERIC / FIXED,最好就使用 DECIMAL 以保持一致性,其它几个混个眼熟
Floatingpoint Types 浮点数类型
进行科学计算,要计算特别大或特别小的数时,会用到浮点数类型,浮点数不是精确值而是近似值,所以它能表示更大范围数值。
具体有两个类型:
-
- FLOAT 浮点数类型,占用4B
- DOUBLE 双精度浮点数,占用8B
如果需要记录精确的数字,比如货币金额,用 DECIMAL 类型。
如果要进行科学计算,要处理很大或很小的数据,而且精确值不重要的话,就用 FLOAT 或 DOUBLE
1.6.4. 布尔类型
MySQL里有个数据类型叫 BOOL / BOOLEAN,储存 是/否 型数据,用布尔值来表示真或假,适用于存储仅具有两种状态的数据。
案例
UPDATE posts
SET is_published = TRUE / FALSE
或
SET is_published = 1 / 0
注意:布尔值其实本质上就是 微整数 TINYINT 的另一种表现形式,TRUE / FALSE 实质上就是 1 / 0。
1.6.5. 枚举和集合类型
需要某个字段从固定的值中取值,可以用到 ENUM() 和 SET() 类型,前者是取一个值,后者是取多个值。
ENUM():从固定一系列值中取一个值
案例
希望 sql_store.products(产品表)里多一个size(尺码)字段,取值为 small/medium/large 中的一个,打开产品表的设计模式,添加size列,数据类型设置为 ENUM('small','medium','large'),产品表会增加一个尺码列,可将其中的值设为small/medium/large(大小写无所谓),但若设为其他值会报错。
SET():ENUM类似,区别是,SET是从固定一系列值中取多个值
注意:知道就行,最好不要用这两个数据类型,问题很多:
1. 修改可选的值(如想增加一个'extra large')会重建整个表,耗费资源
2. 想查询可选值的列表或者想用可选值当作一个下拉列表都会比较麻烦
3. 难以在其它表里复用,其它地方要用只有重建相同的列,之后想修改就要多处修改,又会很麻烦
1.6.6. 日期和时间类型
MySQL 有5种储存日期事件的类型:
1. DATE 有日期没时间
2. TIME 有时间没日期
3. DATETIME 包含日期和时间
4. TIMESTAMP 时间戳,常用来记录一行数据的的插入或最后更新时间
5. YEAR 只存放年份
最后两个的区别是:
-
- TIMESTAMP 占4B,最晚记录2038年,被称为“2038年问题”
- DATETIME 占8B,如果要储存超过2038年的日期时间,就要用 DATETIME
MySQL 提供了许多有用的日期函数。以下列出了常用的日期函数:
- NOW(): 获取当前日期和时间
- CURDATE(): 获取当前日期
- DATE(): 获取日期部分
- DATE_FORMAT(): 格式化输出日期
- DATEDIFF(): 计算两个日期之间的天数
- DATE_ADD(): 在给定日期上增加给定的时间间隔
- DATE_SUB(): 在给定日期上减少给定的时间间隔
- DAY(): 返回日期中天
- MONTH(): 返回月份
- QUARTER(): 返回季节
- YEAR(): 返回年份
- WEEK(): 函数返回给定日期是一年周的第几周
- WEEKDAY(): 函数返回工作日索引
- WEEKOFYEAR(): 函数返回日历周
1.6.7. 二进制大对象类型
用 Blob 类型来储存大的二进制数据,包括PDF,图像,视频等等几乎所有的二进制的文件,具体来说,MySQL里共有4种 Blob 类型,它们的区别在于可储存的最大文件大小:
占用储存 | 最大可储存 |
TINYBOLB | 255B |
BLOB | 65KB |
MEDIUM BLOB | 16MB |
LONG BLOB | 4GB |
注意
通常应该将二进制文件存放在数据库之外,关系型数据库是设计来专门处理结构化关系型数据而非二进制文件。如果将文件储存在数据库内,会有如下问题:
- 数据库的大小将迅速增长
- 备份会很慢
- 性能问题,因为从数据库中读取图片永远比直接从文件系统读取慢
- 需要额外的读写图像的代码
1.6.8. JSON类型
背景:关于JSON
- MySQL可以储存 JSON 文件,JSON 是 JavaScript Object Notation(JavaScript 对象标记法)的简称
- JSON 是一种在互联网上储存和传播数据的简便格式。
- JSON 在网络和移动应用中被大量使用,多数时候手机应用向后端传输数据都是使用 JSON 格式
语法结构:
{
"key1": value1,
"key2": value2,
……
}
- JSON 用大括号{}表示一个对象,里面有多对键值对
- 键 key 必须用引号包裹
- 值 value 可以是数值,布尔值,数组,文本, 甚至另一个对象(形成嵌套 JSON 对象)
1.6.9. 关于数据类型的一些业务场景
1.6.9.1. 整型的取值范围
例如,在存储销售订单时,为了避免存入负数会选择使用unsigned 属性,当我们想要计算每个月销售数量变化时,一个小的数量减去大的数量时会产生负数,MySQL会报错提示计算结果超出范围,解决这个问题需要对数据库参数 sql_mode 设置为 NO_UNSIGNED_SUBTRACTION,允许相减的结果为 signed,这样才能得到最终想要的结果。
1.6.9.2. 自增主键使用什么类型更好?
整型类型在业务中最常见的就是统计物品的数量,还有一个常用的就是作为表的主键,通过auto_increment属性能够实现自增功能。
推荐使用BIGINT作为主键而不是INT,INT 的范围最大在 42 亿的级别,在正式业务中例如一些流水表、日志表,每天 1000W 数据量,420 天后,INT 类型的上限即可达到,当INT达到上限后再进行自增插入就会报重复错误。
另一个问题就是MySQL 8.0 版本前,自增不持久化,自增值可能会存在回溯问题。例如,删除表中自增为 3 的一条记录后,下一个自增值依然为 4(AUTO_INCREMENT=4),自增并不会进行回溯。但若这时数据库发生重启,数据库启动后,自增起始值将再次变为 3,即自增值发生回溯。解决办法就是升级数据库版本,另一个就是不在核心业务表使用自增数据类型做主键。
1.6.9.3. 关于资金的字段使用什么类型存储?
在设计账户余额,零钱等资金字段时,大部分使用的时是DECIMAL 类型,因为能精确到分。在海量互联网业务的设计标准中,并不推荐用 DECIMAL 类型,而是更推荐将 DECIMAL 转化为 整型类型。也就是以分为单位进行存储,例如,1元在数据库中用整型100存储。
使用DECIMAL作为取值范围,金额字段定义为 DECIMAL(8,2) ,只能表示存储最大值为 999999.99,百万级的资金存储。一个统计局的 GDP 金额字段则可能达到数十万亿级别,用类型 DECIMAL 定义,不好统一。
DECIMAL 是通过二进制实现的一种编码方式,计算效率远不如整型高效。使用BIGINT即使用分为单位存储,也能存储千兆(1兆 = 1万亿)级别的金额。而且所有金额相关字段都是定长字段,占用 8 个字节,存储高效。
使用定长存储的好处:
例如,当前数据库记录存储的长度不一致,当记录4更新后数据长度增加了,原先的空间无法容纳更新后的记录了,会将记录4删除再寻找新的空间给记录4使用。原记录4的空间变成碎片空间,无法继续使用,除非人为地进行表空间的碎片整理。
小数点怎么表示:
使用 BIG INT 存储金额字段的时候金额是存储的整数,小数点完全可以由前端进行处理并展示。
1.6.9.4. 用户性别,状态等有限定值的字段
当使用INT型存储,存在两个问题:表达不清晰和容易出现脏数据。MySQL 8.0.16 版本开始,数据库原生提供 CHECK 约束功能,可以方便地进行有限状态列类型的设计。
CONSTRAINT `user_chk_1` CHECK (((`sex` = _utf8mb4'M') or (`sex` = _utf8mb4'F')))
这样就约束了性别,确保在插入或更新数据时,sex 列的值只能是 'M'(男性)或 'F'(女性)。
1.6.9.5. 账户密码的存储
在数据库表中直接存储密码容易造成用户信息泄露的问题,只使用MD5加密是不够的。在存储密码还需要加盐(salt),每个公司的盐值是不同的,盐值被泄露密码也容易被破解。最好使用动态盐 + 非固定加密算法。格式如下:
$salt$cryption_algorithm$value
- $salt:表示动态盐,每次用户注册时业务产生不同的盐值,并存储在数据库中。若做得再精细一点,可以动态盐值 + 用户注册日期合并为一个更为动态的盐值。
- $cryption_algorithm:表示加密的算法,如 v1 表示 MD5 加密算法,v2 表示 AES256 加密算法,v3 表示 AES512 加密算法等。
- $value:表示加密后的字符串。
1.6.9.6. 业务中DATETIME vs TIMESTAMP怎么选?
更建议使用DATETIME。如果要将时间精确到毫秒TIMESTAMP 要 7 个字节,和 DATETIME 8 字节差不太多,而且距离2038年时间也很近了,DATETIME的时区问题可以由前端或者服务转化一次。
如果使用 TIMESTAMP 必须显式的设置时区,不要使用系统时间,使用默认的操作系统时区,则每次通过时区计算时间时,要调用操作系统底层系统函数 __tz_convert(),而这个函数需要额外的加锁操作,以确保这时操作系统时区没有修改。所以,当大规模并发访问时,由于热点资源竞争,会产生性能抖动。
SET time_zone = 'Asia/Shanghai';
最好在每张业务核心表都增加一个 DATETIME 类型的 last_modify_date 字段,并设置修改自动更新机制,能够知道每个用户记录最近一次更新的时间,方便后续要进行的操作。