MySQL 那些常见的错误设计规范

依托于互联网的发达,咱们能够随时随地利用一些等车或坐地铁的碎片时间学习以及了解资讯。同时发达的互联网也方便人们可以快速分享本身的知识,与相同爱好和需求的朋友们一块儿共同讨论。html

可是过于方便的分享也让知识变得五花八门,很容易让人接收到错误的信息。这些错误最多的都是由于技术发展迅速,并且没有空闲时间去及时更新已经发布的内容所致使。为了不给后面学习的人形成误解,咱们今天来看一看 MySQL 设计规范中几个常见的错误例子。sql

主键的设计

错误的设计规范:主键建议使用自增 ID 值,不要使用 UUID,MD5,HASH,字符串做为主键数据库

这个设计规范在不少文章中都能看到,自增主键的优势有占用空间小,有序,使用起来简单等优势。编程

下面先来看看自增主键的缺点:segmentfault

  • 自增值因为在服务器端产生,须要有一把自增的 AI 锁保护,若这时有大量的插入请求,就可能存在自增引发的性能瓶颈,因此存在并发性能问题;
  • 自增值作主键,只能在当前实例中保证惟一,不能保证全局惟一,这就致使没法在分布式架构中使用;
  • 公开数据值,容易引起安全问题,若是咱们的商品 ID 是自增主键的话,用户能够经过修改 ID 值来获取商品,严重的状况下能够知道咱们数据库中一共存了多少商品。
  • MGR(MySQL Group Replication) 可能引发的性能问题;

由于自增值是在 MySQL 服务端产生的值,须要有一把自增的 AI 锁保护,若这时有大量的插入请求,就可能存在自增引发的性能瓶颈。好比在 MySQL 数据库中,参数 innodb_autoinc_lock_mode 用于控制自增锁持有的时间。虽然,咱们能够调整参数 innodb_autoinc_lock_mode 得到自增的最大性能,可是因为其还存在其它问题。所以,在并发场景中,更推荐 UUID 作主键或业务自定义生成主键。安全

咱们能够直接在 MySQ L使用 UUID() 函数来获取 UUID 的值。服务器

MySQL> select UUID();
+--------------------------------------+
| UUID()                               |
+--------------------------------------+
| 23ebaa88-ce89-11eb-b431-0242ac110002 |
+--------------------------------------+
1 row in set (0.00 sec)

须要特别注意的是,在存储时间时,UUID 是根据时间位逆序存储, 也就是低时间低位存放在最前面,高时间位在最后,即 UUID 的前 4 个字节会随着时间的变化而不断“随机”变化,并不是单调递增。而非随机值在插入时会产生离散 IO,从而产生性能瓶颈。这也是 UUID 对比自增值最大的弊端。架构

为了解决这个问题,MySQL 8.0 推出了函数 UUID_TO_BIN,它能够把 UUID 字符串:并发

  • 经过参数将时间高位放在最前,解决了 UUID 插入时乱序问题;
  • 去掉了无用的字符串"-",精简存储空间;
  • 将字符串其转换为二进制值存储,空间最终从以前的 36 个字节缩短为了 16 字节。

下面咱们将以前的 UUID 字符串 23ebaa88-ce89-11eb-b431-0242ac110002 经过函数 UUID_TO_BIN 进行转换,获得二进制值以下所示:框架

MySQL> SELECT UUID_TO_BIN('23ebaa88-ce89-11eb-b431-0242ac110002',TRUE) as UUID_BIN;
+------------------------------------+
| UUID_BIN                           |
+------------------------------------+
| 0x11EBCE8923EBAA88B4310242AC110002 |
+------------------------------------+
1 row in set (0.01 sec)

除此以外,MySQL 8.0 也提供了函数 BIN_TO_UUID,支持将二进制值反转为 UUID 字符串。

虽然 MySQL 8.0 版本以前没有函数 UUID_TO_BIN/BIN_TO_UUID,仍是能够经过自定义函数的方式解决。应用层的话能够根据本身的编程语言编写相应的函数。

固然,不少同窗也担忧 UUID 的性能和存储占用的空间问题,这里我也作了相关的插入性能测试,结果以下表所示:

能够看到,MySQL 8.0 提供的排序 UUID 性能最好,甚至比自增 ID 还要好。此外,因为 UUID_TO_BIN 转换为的结果是16 字节,仅比自增 ID 增长 8 个字节,最后存储占用的空间也仅比自增大了 3G。

并且因为 UUID 能保证全局惟一,所以使用 UUID 的收益远远大于自增 ID。可能你已经习惯了用自增作主键,可是在并发场景下,更推荐 UUID 这样的全局惟一值作主键。

固然了,UUID虽好,可是在分布式场景下,主键还须要加入一些额外的信息,这样才能保证后续二级索引的查询效率,推荐根据业务自定义生成主键。可是在并发量和数据量没那么大的状况下,仍是推荐使用自增 UUID 的。你们更不要觉得 UUID 不能当主键了。

金融字段的设计

错误的设计规范:同财务相关的金额类数据必须使用 decimal 类型 因为 float 和 double 都是非精准的浮点数类型,而 decimal 是精准的浮点数类型。因此通常在设计用户余额,商品价格等金融类字段通常都是使用 decimal 类型,能够精确到分。

可是在海量互联网业务的设计标准中,并不推荐用 DECIMAL 类型,而是更推荐将 DECIMAL 转化为整型类型。 也就是说,金融类型更推荐使用用分单位存储,而不是用元单位存储。如1元在数据库中用整型类型 100 存储。

下面是 bigint 类型的优势:

  • decimal 是经过二进制实现的一种编码方式,计算效率不如 bigint
  • 使用 bigint 的话,字段是定长字段,存储高效,而 decimal 根据定义的宽度决定,在数据设计中,定长存储性能更好
  • 使用 bigint 存储分为单位的金额,也能够存储千兆级别的金额,彻底够用

枚举字段的使用

错误的设计规范:避免使用 ENUM 类型

在之前开发项目中,遇到用户性别,商品是否上架,评论是否隐藏等字段的时候,都是简单的将字段设计为 tinyint,而后在字段里备注 0 为何状态,1 为何状态。

这样设计的问题也比较明显:

  • 表达不清:这个表多是其余同事设计的,你印象不是特别深的话,每次都须要去看字段注释,甚至有时候在编码的时候须要去数据库确认字段含义
  • 脏数据:虽然在应用层能够经过代码限制插入的数值,可是仍是能够经过sql和可视化工具修改值

这种固定选项值的字段,推荐使用 ENUM 枚举字符串类型,外加 SQL_MODE 的严格模式

在MySQL 8.0.16 之后的版本,能够直接使用check约束机制,不须要使用enum枚举字段类型

并且咱们通常在定义枚举值的时候使用"Y","N"等单个字符,并不会占用不少空间。可是若是选项值不固定的状况,随着业务发展可能会增长,才不推荐使用枚举字段。

索引个数限制

错误的设计规范:限制每张表上的索引数量,一张表的索引不能超过 5 个

MySQL 单表的索引没有个数限制,业务查询有具体须要,建立便可,不要迷信个数限制

子查询的使用

错误的设计规范:避免使用子查询

其实这个规范对老版本的 MySQL 来讲是对的,由于以前版本的 MySQL 数据库对子查询优化有限,因此不少 OLTP 业务场合下,咱们都要求在线业务尽量不用子查询。

然而,MySQL 8.0 版本中,子查询的优化获得大幅提高,因此在新版本的MySQL中能够放心的使用子查询。

子查询相比 JOIN 更易于人类理解,好比咱们如今想查看2020年没有发过文章的同窗的数量

SELECT COUNT(*)
FROM user
WHERE id not in (
    SELECT user_id
    from blog
    where publish_time >= "2020-01-01" AND  publish_time <= "2020-12-31"
)

能够看到,子查询的逻辑很是清晰:经过 not IN 查询文章表的用户有哪些。

若是用 left join 写

SELECT count(*)
FROM user LEFT JOIN blog
ON user.id = blog.user_id and blog.publish_time >= "2020-01-01" and blog.publish_time <= "2020-12-31"
where blog.user_id is NULL;

能够发现,虽然 LEFT JOIN 也能完成上述需求,但不容易理解。

咱们使用 explain查看两条 sql 的执行计划,发现都是同样的

经过上图能够很明显看到,不管是子查询仍是 LEFT JOIN,最终都被转换成了left hash Join,因此上述两条 SQL 的执行时间是同样的。即,在 MySQL 8.0 中,优化器会自动地将 IN 子查询优化,优化为最佳的 JOIN 执行计划,这样一来,会显著的提高性能。

总结

阅读完前面的内容相信你们对 MySQL 已经有了新的认知,这些常见的错误能够总结为如下几点:

  • UUID 也能够当主键,自增 UUID 比自增主键性能更好,多占用的空间也可忽略不计
  • 金融字段除了 decimal,也能够试试 bigint,存储分为单位的数据
  • 对于固定选项值的字段,MySQL8 之前推荐使用枚举字段,MySQL8 之后使用check函数约束,不要使用 0,1,2 表示
  • 一张表的索引个数并无限制不能超过5个,能够根据业务状况添加和删除
  • MySQL8 对子查询有了优化,能够放心使用。

推荐阅读

实操笔记:为 NSQ 配置监控服务的心路历程

go-zero:开箱即用的微服务框架

相关文章
相关标签/搜索