目录

  • 前言
  • MySQL表、索引 优化实战
    • 优化前 - 表、索引结构
      • 表优化思路
      • 非聚簇索引优化思路
      • 组合索引优化思路
        • 认识key_len
          • 依据最左匹配选择 只使用一列也能走联合索引
          • 依据最左匹配选择 使用前面两列
          • 使用联合索引所有列查询
        • 范围查询对组合索引的影响
          • 将查询范围扩大 直接导致全表扫描
    • 优化后 - 表、索引结构
      • 优化后组合索引全字段查询
      • 优化后组合索引无法满足最左匹配下SQL写法
      • 优化后扩大查询范围依然能走索引
    • 总结

前言

就目前中国市场,作为每一个开发人员都需要去会的技能 MySQL ,大家工作中都会需要 建表建索引,有的同学可能要说了:“这玩意谁不会建呢?太简单了!”。若是如此那真是太好了,我带团队也有几年了前前后后经历了一些人,超过85%的人建表、建索引 真的只是停留在会创建。创建出来的表、索引 在我看来也是问题一大堆。
所以,咱们今天就来好好聊聊如何创建一张漂亮的表,同时给他上一个 高效的有逼格的索引。

MySQL表、索引 优化实战

咱们简单拿一个订单信息表来做基础优化(假设没有别的业务影响),当然实际业务上会比这种案例场景复杂一点,希望大家可以用这次优化思路,持续延伸、扩展。同时也希望大家可以先看看下面的表结构自己判断下哪些地方是可以优化的,对比一下下面的优化思路。

优化前 - 表、索引结构

-- 用户订单表
CREATE TABLE `user_order` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id自增长',`order_number` varchar(32) DEFAULT NULL COMMENT '订单编号',`user_id` bigint(20) DEFAULT NULL COMMENT '用户id',`origin` varchar(64) DEFAULT 'google play' COMMENT '订单来源(运营市场分析)',`type` int(2) DEFAULT '0' COMMENT '订单类型,0-普通订单;1-xxxxx;2-xxxxxx',`source_id` varchar(32) DEFAULT NULL COMMENT '下单设备类型: Android, H5,IOS',`amount` bigint(20) DEFAULT NULL COMMENT '申请金额',`promotion_code` varchar(32) DEFAULT NULL COMMENT '优惠码',`state` varchar(32) DEFAULT NULL COMMENT '状态',`applied_at` datetime DEFAULT NULL COMMENT '申请时间',`cancelled_at` datetime DEFAULT NULL COMMENT '取消时间',`closed_at` datetime DEFAULT NULL COMMENT '关闭时间',`created_at` datetime NOT NULL COMMENT '创建时间',`updated_at` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE,KEY `idx_on_user_id` (`user_id`) USING BTREE,KEY `idx_on_order_number` (`order_number`) USING BTREE,KEY `idx_on_applied_at_and_source_id_and_state` (`applied_at`,`source_id`,`state`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='用户订单表';

表优化思路

  • id:id 主键字段,自增长,带主键索引

    • 这个字段没有问题,希望大家建任何MySQL的表都能加上一个 自增长的主键ID ,且这个主键ID 不要用UUID不要不创建主键
  • order_number:varchar(32) DEFAULT NULL
    • 首先我们一个系统生成订单号一般长度都是 固定 的,不可能一会长度5、一会长度30。 所以这里建议根据实际长度 使用 char(len) 类型。因为varchar是一个 可变长 字段,MySQL需要用 额外的两个字节 去保存长度。 使用 char类型对存储空间、查询效率均有提升。
    • 其次这个order_number,在业务逻辑上应该是 不可能为NULL 的,所以我们应该对order_number 设置 NOT NULL ,第一保证的业务的正确性,第二节省了一个字节,因为如果一个字段允许为NULL,MySQL需要额外的 一个字节标记 该字段是否为NULL。
  • user_id:bigint(20) DEFAULT NULL
    • 同理根据业务可确定该字段需要设置 NOT NULL
  • origin :varchar(64) DEFAULT ‘google play’
    • 既然都设置默认值了 逻辑上也不可能为NULL了 所以 需要设置 NOT NULL
    • 运营的渠道统计分析、这个字段长度要更具实际场景考虑64是否太长了?
    • 如业务允许可以用关联表管理渠道Name,这里直接写入渠道的ID即可,因为大多数渠道应该都是 重复 的,这个字段建索引的概率也不大,用int类型ID,节省存储空间、同时 查询效率提升 。因为内存中 数值类型的判断等于的效率远高于字符类型
  • type:int(2) DEFAULT ‘0’
    • 字段名使用到了MySQL的 关键字 type,建议改为 order_type
    • 既然都设置默认值了 逻辑上也不可能为NULL了 所以 需要设置 NOT NULL
  • source_id:varchar(32) DEFAULT NULL
    • 根据描述这个字段完全可以用int类型1、2、3代替Android、H5、IOS
    • 如果实在不想使用int代替,也可以修改字段长度,因为实际场景中(Android、H5、IOS)这三个字段的长度完全是已知的 所以可以改成 char 类型。
    • 逻辑上也不可能为NULL了 所以 需要设置 NOT NULL
  • amount:bigint(20) DEFAULT NULL
    • 这个字段类型要不要替换成 decimal 咱们就不聊了,也许人家就是整数的,这个根据实际业务确定
    • 逻辑上金额需要设置一个 默认 0,然后设置 NOT NULL
  • promotion_code:varchar(32) DEFAULT NULL
    • 这个字段没啥好说的,根据实际业务来吧
  • state:varchar(32) DEFAULT NULL
    • 逻辑上状态也不可能为NULL,需要设置一个 默认的初始状态 ,同时需要设置 NOT NULL
    • 也可以考虑替换为int类型的码表代替。(自行判断)
  • applied_at: datetime DEFAULT NULL
    • 逻辑上这个时间应该不可能为 NULL ,所以 需要设置 NOT NULL
  • cancelled_at:datetime DEFAULT NULL
    • 这个根据实际业务来,没啥好优化的
  • closed_at:datetime DEFAULT NULL
    • 这个根据实际业务来,没啥好优化的
  • created_at:datetime NOT NULL
    • 这个字段没啥问题,建议大家的数据库表都加上创建时间字段
  • updated_at:datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT
    • 这个字段没啥问题,建议大家的数据库表都加上修改时间字段,且自动根据当前时间更新

非聚簇索引优化思路

  • KEY idx_on_user_id (user_id) USING BTREE

    • user_id索引,没啥问题,正常的业务中一定有根据user_id查询的需求
  • KEY idx_on_order_number (order_number) USING BTREE
    • order_number索引,逻辑上订单号是不能重复的,所以这里的索引应该修改为 唯一索引,数据安全性、查询效率均有提升
  • KEY idx_on_applied_at_and_source_id_and_state (applied_at,source_id,state) USING BTREE
    • 到了这次分享的关键点了,这个联合索引最左侧的是下单时间(applied_at),而这个字段查询多半是会使用范围查询 “>”、"<" 之类的,那问题就来了,如果我把这三个字段都用到查询条件中,可能会出现的场景就是联合索引没有使用全,因为applied_at的 “>”、"<" 查询,导致索引只使用了applied_at字段,并没有去使用后面的source_idstate,所以我的优化思路是: KEY idx_on_source_id_and_state_and_applied_at (applied_at,source_id,state) USING BTREE。

当然这样优化我想大家可能有很多疑问,那么接下来的篇幅中将会针对如上疑问进行一一讲解

组合索引优化思路

注:以下案例无特殊说明均使用优化前的 user_order 表的字段和索引

认识key_len

  • key_len:数值提示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些列。 举例来说,user_order的联合索引 idx_on_applied_at_and_source_id_and_state 由 applied_at 和 source_id 以及 state 三个列组成,如果在索引中所有列都用到了则 key_len = 三个字段的长度总和,如只用到了applied_at 则 key_len = datetime类型在数据库中所占用的字节数。

key_len Demo 如下 ↓↓↓

依据最左匹配选择 只使用一列也能走联合索引
EXPLAIN SELECT * from user_order WHERE applied_at = '2021-10-31' ;


依据最左匹配选择 使用前面两列
EXPLAIN SELECT * from user_order WHERE applied_at = '2021-10-31' AND source_id = 'android';

上面这个长度可能有同学会问了 为什么是 105 呢? 已知 source_id 是 varchar(32) 且允许字段为NULL,使用的是utf8编码,一个中文在索引中占用字节数为3 ,(32 * 3 ) + 2(varchar类型需要存储长度) + 1(是否为null标识) = 99。
已知第一个字段 applied_at 索引长度为 5 , 5 + 99 = 104


使用联合索引所有列查询
EXPLAIN SELECT * from user_order WHERE applied_at = '2021-10-31' AND source_id = 'android' and state in('canceled','closed');


范围查询对组合索引的影响

-- 将等于修改为大于
EXPLAIN SELECT * from user_order WHERE applied_at > '2021-10-31' AND source_id = 'android';EXPLAIN SELECT * from user_order WHERE applied_at > '2021-10-31'  AND source_id = 'android' and state in('canceled','closed');

根据如上案例得知,如果我们使用 “>”、"<" 范围查询时 有可能 导致组合索引无法完全生效。
当然这里如果将 > ‘2021-10-31’ 修改为 >= ‘2021-11-01’ 是可能会去走后续索引的,但是也并不是一定的。


将查询范围扩大 直接导致全表扫描
EXPLAIN SELECT * from user_order WHERE applied_at > '2021-06-31' AND source_id = 'android' and state in('canceled');

如上SQL 直接不走索引选择了全表扫描,可是 canceled 这种状态,在业务数据中一般是比较少的,如果可以走索引再加上时间 我们依然可以过滤掉95%以上的数据,但 结果是优化器选择了全表扫描。

接下来我们按照上面对表和索引进行优化 然后看看执行情况。

优化后 - 表、索引结构

CREATE TABLE `user_order` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id自增长',`order_number` char(32) NOT NULL COMMENT '订单编号',`user_id` bigint(20) NOT NULL COMMENT '用户id',`origin` varchar(32) NOT NULL DEFAULT 'google play' COMMENT '订单来源(运营市场分析)',`order_type` int(2) NOT NULL DEFAULT '0' COMMENT '订单类型,0-普通订单;1-xxxxx;2-xxxxxx',`source_id` char(8) NOT NULL COMMENT '下单设备类型: Android, H5,IOS',`amount` bigint(20) NOT NULL DEFAULT '0' COMMENT '申请金额',`promotion_code` varchar(32) DEFAULT NULL COMMENT '优惠码',`state` varchar(20) NOT NULL DEFAULT 'init' COMMENT '状态',`applied_at` datetime NOT NULL COMMENT '申请时间',`cancelled_at` datetime DEFAULT NULL COMMENT '取消时间',`closed_at` datetime DEFAULT NULL COMMENT '关闭时间',`created_at` datetime NOT NULL COMMENT '创建时间',`updated_at` datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',PRIMARY KEY (`id`) USING BTREE,UNIQUE KEY `idx_on_order_number` (`order_number`) USING BTREE,KEY `idx_on_user_id` (`user_id`) USING BTREE,KEY `idx_on_applied_at_and_source_id_and_state` (`source_id`,`state`,`applied_at`) USING BTREE
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='借款信息表';

表结构咱们就不看了 直接进入主题看看修改后的组合索引,调整了顺序,废话不多说咱们来看看实际效果

优化后组合索引全字段查询

EXPLAIN SELECT * from user_order WHERE source_id = 'android' and state in('closed') AND applied_at > '2021-10-31' ;

(8 * 3) + (20 * 3 + 2) + 5 = 91 索引长度大大减少的同时,还能把整个联合索引走全了。

当然这个时候肯定就有人提出问题了,我当初把 applied_at 放到最左边 是因为之后可能有场景会直接根据时间查询 或者,根据 applied_at 和 source_id 两个条件去查询 这种场景你怎么办呢?

答:依据业务分析 source_id 和 state 的类型都不多,且业务在查询前就是已知的(可能代码里面有枚举已经提前写好了可能存在的值),那么当咱们只需要根据 applied_at 和 source_id 进行查询时 SQL使用如下写法。

优化后组合索引无法满足最左匹配下SQL写法

EXPLAIN SELECT * from user_order WHERE source_id in ('android','ios','h5') and state in('canceled') and applied_at > '2021-10-31';

把 source_id 所有可能出现的类型均加入查询条件即可,如果 也需要只根据 applied_at 查询 则将state 所有可能的值写入查询条件中即可。 如果这里选择用int类型来代替 source_id 与 state 效率更高,索引长度更短。


优化后扩大查询范围依然能走索引

EXPLAIN SELECT * from user_order WHERE source_id = 'android' and state = 'canceled' AND applied_at > '2021-06-31' ;

这条SQL在之前的查询中并未使用索引,因为查询时间范围太大了,但是优化后效果明显!!

总结

注:以上优化仅是提供思路,实际业务中咱们需要根据实际业务场景带入 来建表、建索引,切忌眼中无业务,只知道莽;
或者只知道建单列的索引,不懂组合索引的优势, 这里带入MySQL 索引合并 大家可以查一下 如果不使用组合索引而去用多个单列索引来完成查询字段的覆盖,实际对MySQL的性能还是会造成很大的影响的

MySQL实战——表、索引创建与优化相关推荐

  1. mysql数据库表的创建与查看

    mysql数据库表的创建与查看 一.mysql查看 查看所有数据库 show databases 切换数据库 use 数据库名 查看该数据库下所有的表名 show tables 查看表的结构 desc ...

  2. mysql 建表时创建唯一索引

    Mysql建表与索引使用规范整理 一,设计表规范: MySQL建表,字段需设置为非空,需设置字段默认值: MySQL建表,字段需NULL时,需设置字段默认值,默认值不为NULL: MySQL建表,如果 ...

  3. mysql 派生表 索引_MySQL 索引初探

    MYSQL索引 什么是索引 为什么要建立索引 索引的优缺点和使用原则 索引的分类 单列索引:一个索引只包含单个列,但一个表中可以有多个单列索引. 普通索引:MySQL中基本索引类型,没有什么限制,允许 ...

  4. 命令行操作MySQL - 《表索引》汇总

    这是命令行操作MySQL数据库系列博客的第十二篇,今天这篇博客记录<表索引>如何进行操作.(普通索引.唯一索引.全文索引.多列索引.隐藏索引.删除索引和修改索引) 目录 索引概述 创建和查 ...

  5. MYSQL 分析表、检查表和优化表

    1. 对表进行优化 ( 优化表主要作用是消除删除或者更新造成的空间浪费) 2. 对表进行分析(分析关键字的分布, 分析并存储MyISAM和BDB表中键的分布) 3. 对表进行检查(检查表的错误,并且为 ...

  6. mysql 查看表是否存在_MySQL优化篇二

    单表优化 最佳左前缀原则为,保持索引的定义和使用顺序的一致性 将含In的范围查询,放到where条件语句的最后. 索引需要逐步优化 两表优化 小表驱动大表 避免索引失效的一些原则: 复合索引,不要跨列 ...

  7. mysql 重置表索引_MySQL如何进行索引重建操作?

    在MySQL数据库中,没有类似于SQL Server数据库或Oracle数据库中索引重建的语法(ALTER INDEX ... REBUILD),那么在MySQL数据库中,是否有什么方式重建索引呢? ...

  8. mysql 重置表索引_MySQL管理表和索引

    MySQL管理表和索引 SQL语句: 数据库 表 索引 视图 DML创建数据库: CREATE DATABASE|SCHEMA [IF NOT EXISTS] db_name [CHARACTER S ...

  9. mysql 大表更新数据类型_MySQL优化之表结构优化的5大建议(数据类型选择讲的很好)...

    殊不知,在N年前被奉为"圣经"的数据库设计3范式早就已经不完全适用了.这里我整理了一些比较常见的数据库表结构设计方面的优化技巧,希望对大家有用. 由于MySQL数据库是基于行(Ro ...

最新文章

  1. 无法使用BIPublisher开发报表
  2. 计算机设置内存储器的必要性,计算机设置内存储器的必要性
  3. CRT exCRT模板
  4. python基础教程:强制数据类型转换教程及实例
  5. 如何解决Filezilla 与虚拟机连接不上的问题
  6. imessage_如何在iPhone和iPad上的iMessage组中提及某人
  7. 中科大 计算机网络4 网络核心Core 分组交换 电路交换
  8. e-mobile帐号状态存在异常_企业微信添加好友提示”操作异常”怎么办?
  9. fedora nginx php,在fedora16下安裝nginx + php-fpm
  10. shell学习之-sed用法解析_Shell脚本之sed的使用
  11. 如何使用Camtasia给视频打码
  12. 计算机速录学什么,学速录需要哪些能力
  13. 真正的焦虑感来自对比
  14. CMake file命令参数GLOB和GLOB_RECURSE
  15. python中获取中位数的两种方法
  16. 电脑QQ正常上网但浏览器无法上网解决办法
  17. 【SVAC】国家视频编解码标准SVAC的特色和优势
  18. Unity中计算面法线与顶点法线
  19. 文明与征服君士坦丁阵容搭配推荐 文明与征服君士坦丁攻略
  20. 运行vue项目时,如果一直报eslint语法错误的解决方案

热门文章

  1. 加速度计和陀螺仪区别
  2. 22岁对于人生的感悟
  3. Android播放音频工具类
  4. [渝粤教育] 中北大学 商业模式设计与创新 参考 资料
  5. 1t硬盘怎么分区最好_1T的硬盘空间怎么分区比较合理
  6. 计算机操作系统与嵌入式操作系统的区别,嵌入式操作系统与通用计算机操作系统的区别...
  7. ViewPager+Fragment添加一行若干小红球滚动指示器指示当前ViewPager翻阅位置(可供第三方使用的类)
  8. 自适应模糊神经网络的设计
  9. 联想台式计算机装系统,联想台式电脑用u盘怎么重装win7系统教程
  10. 计蒜客题解——T1213:拯救行动