MySQL实战——表、索引创建与优化

前言

就目前中国市场,作为每一个开发人员都需要去会的技能 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的性能还是会造成很大的影响的

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
目录 第1篇 基础篇 第1章 开发环境 2 1.1 AppServ——PHP集成化安装包 3 实例001 通过AppServ配置PHP开发环境 3 实例002 AppServ应用技巧 6 实例003 第1个PHP程序 7 1.2 XAMPP——PHP集成化安装包 8 实例004 通过XAMPP配置PHP开发环境 8 实例005 测试XAMPP是否安装成功 11 实例006 XAMPP应用技巧 12 实例007 第2个PHP程序 13 1.3 IIS+PHP+MySQL——独立搭建PHP 开发环境 14 实例008 安装PHP 14 实例009 安装MySQL 15 实例010 安装IIS 21 实例011 第3个PHP程序 23 1.4 LAMP——独立配置PHP开发环境 24 实例012 安装Apache服务器 24 实例013 安装MySQL数据库服务器 26 实例014 安装PHP.50 28 实例015 第4个PHP程序 30 1.5 XAMPP——Linux版PHP集成化安装包 31 实例016 XAMPP——Linux下PHP开发环境的集成化 31 实例017 Linux操作系统下启动XAMPP 32 实例018 设置MySQL数据库root用户的密码 33 实例019 第5个PHP程序 34 1.6 Dreamweaver开发工具 35 实例020 Dreamweaver中编码格式的选择 35 实例021 Dreamweaver中创建格 36 实例022 在格中插入宠物照片 38 实例023 Dreamweaver创建单 40 实例024 Dreamweaver中创建和附加CSS样式 42 实例025 Dreamweaver控制弹出信息 45 实例026 Dreamweaver控制浏览器的窗口 46 实例027 通过Dreamweaver创建站点 47 实例028 通过Dreamweaver开发第1个PHP程序 48 1.7 Zend Studio开发工具 50 实例029 安装Zend Studio 50 实例030 Zend Studio创建PHP项目 52 实例031 Zend Studio编码格式的转换 56 实例032 Zend Studio中快捷键的运用 57 实例033 Zend Studio中部署Apache服务器 60 第2章 PHP基础 63 2.1 基本语法 64 实例034 在页面中打印PHP的配置信息 64 实例035 在页面中打印服务器时间 65 实例036 在页面中打印当前执行的PHP文件名 67 实例037 区分单引号和双引号 68 实例038 动态输出JavaScript代码 69 实例039 当数字遇到了字符串 70 实例040 PHP程序员的基础——变量的应用 72 实例041 打印系统环境变量信息print_r($_ENV) 73 实例042 使用可变变量输出“I Like PHP!” 73 实例043 使用转义字符输出特殊字符 74 实例044 使用常量指定PI的值计算圆的面积 75 2.2 运算符 75 实例045 自定义数字的加密/解密算法 76 实例046 比较两个时间戳的大小 77 实例047 使用条件运算符判断数字的奇偶性 78 实例048 判断用户是否具有后台管理权限 79 实例049 打印随机组合生日祝福语 80 实例050 打印2000~2020年的所有闰年 81 实例051 前置运算符和后置运算符的区别 82 实例052 使用位运算对数字进行加密和解密 83 2.3 包含语句 84 实例053 提高代码重用率 84 实例054 包含数据库连接文件 85 实例055 包含网站头文件 86 实例056 包含网站尾文件 87 实例057 包含网站的主文件 88 2.4 条件语句 89 实例058 员工生日提醒 89 实例059 考试成绩评定标准 90 实例060 控制登录用户权限 91 实例061 网页框架的制作 92 实例062 图片验证码 93 实例063 健康生活提醒 95 2.5 循环控制 96 实例064 员工生日列 96 实例065 员工详细信息浏览 97 实例066 员工信息的批量删除 98 实例067 格的动态创建 99 实例068 SESSION购物车中数据的读取 100 实例069 员工信息的管理 102 实例070 网页版九九乘法 104 实例071 读取数组购物车中的数据 105 实例072 图像验证码的生成 106 2.6 跳转语句 107 实例073 控制页面中情图的输出 107 实例074 控制页面中数据的输出数量 108 实例075 动态改变页面中单元格的背景颜色 109 实例076 屏蔽偶数次的数据输出 110 实例077 跳过数据输出中指定的记录 111 实例078 执行指定次数的循环 112 2.7 自定义函数 113 实例079 自定义函数截取中文字符串 113 实例080 公告标题的截取 114 实例081 论坛内容的简短输出 116 实例082 自定义函数过滤字符串 117 2.8 字符串 118 实例083 过滤论坛帖子中的空白和特殊字符 118 实例084 对论坛帖子的内容进行转义 119 实例085 还原论坛中帖子的内容 120 实例086 截取论坛标题 121 实例087 分割、合成字符串 121 实例088 查询关键字描红 122 实例089 统计查询关键字的出现次数 123 实例090 获取上传文件的后缀 124 实例091 统一上传文件名称的大小写 125 实例092 论坛中直接输出HTML脚本 126 实例093 统计帖子标题的长度 127 实例094 计算论坛帖子的字节数 128 实例095 实现帖子内容的定位查找 129 实例096 去除帖子标题的首尾空格 129 实例097 货币数据的格式化输出 130 实例098 日期、时间的格式化输出 131 实例099 比对论坛中帖子的相似度 132 实例100 对用户注册的密码进行加密和解密 132 实例101 保护URL地址中传递的参数 133 实例102 解析URL地址中传递的编码参数 134 实例103 转义帖子中的特殊字符 135 实例104 获取任意字符的ASCII码 136 实例105 查找指定ASCII码对应的字符 136 实例106 合理定义上传文件的名称 137 实例107 通过异或方式对用户注册密码进行加密 139 实例108 统一英文注册用户首字母的大小写 139 实例109 字符串与数字之间的转换 140 实例110 通过正则达式对字符串进行匹配查找 141 实例111 通过IP地址查找主机所在地 142 实例112 解决用substr()函数对中文字符串截取时出现乱码的问题 143 实例113 字符串与HTML标记相互转换 144 实例114 运用PHP 5.0新型字符串输出XML数据 145 实例115 判断字符串中是否存在指定子串 146 2.9 正则达式 147 实例116 验证电话号码的格式是否正确 147 实例117 验证Email地址格式是否正确 148 实例118 验证IP地址是否有效 149 实例119 统计关键字的查询结果 150 实例120 计算查询操作的执行时间 151 实例121 查询关键字描红 152 实例122 判断上传文件的类型 153 实例123 判断邮政编码格式是否正确 154 2.10 数组 154 实例124 创建并输出数组 155 实例125 统计数组元素个数 156 实例126 向数组中添加元素 156 实例127 将数组中指定索引位置的元素替换 157 实例128 获取数组中最后一个元素 158 实例129 去除数组中的重复元素 158 实例130 字符串与数组的转换 159 实例131 对数组元素进行随机排序 160 实例132 随机抽取数组中元素 161 实例133 二维数组的输出 162 实例134 获取数组当前的键名和值 162 实例135 检测数组中是否存在某个值 163 实例136 获取数组中的当前单元 164 实例137 从数组中随机取出元素 165 实例138 合并数组 166 实例139 拆分数组 167 实例140 遍历数组 168 2.11 日期和时间 169 实例141 设置系统的当前时间 169 实例142 将日期和时间转换为时间戳 170 实例143 获取系统中的特定日期和时间 171 实例144 比较时间的大小 172 实例145 计算考试时间 173 实例146 倒计时 173 实例147 网页闹钟 174 实例148 计算程序运行时间 176 第3章 Web页面交互 178 3.1 获取单元素的值 179 实例149 设计论坛登录界面 179 实例150 可以上传图片的单 180 实例151 通过POST方法提交单元素 182 实例152 通过$_POST方法获取单元素 182 实例153 通过GET方法提交单元素 184 实例154 通过$_GET方法获取单元素 185 3.2 文本框组件的应用 186 实例155 以文本域的形式显示数据信息 186 实例156 限制多行文本域输入的字符个数 187 实例157 设置文本框的只读属性 188 实例158 自动计算金额 189 实例159 为文本框设置默认值 190 实例160 设置文本框的样式 191 实例161 文本域的滚动条 192 3.3 下拉列的应用 193 实例162 省市级联动菜单 193 实例163 省市县级联动菜单 194 实例164 应用下拉列选择所要联机的网站 196 实例165 可输入字符的下拉菜单 197 实例166 设置下拉列的默认值 198 实例167 设置下拉列的样式 199 实例168 下拉列打开窗口 200 实例169 Tab键在文本域中的体现 201 3.4 单元素的动态操作 203 实例170 投票系统中单选按钮与复选框的应用 203 实例171 选中单选按钮后显示其他单元素 205 实例172 不提交单获取单选按钮的值 207 实例173 实现复选框中的全选、反选和不选 208 实例174 隐藏域提交用户的ID值 210 实例175 图像域替代提交按钮 211 实例176 跳转菜单实现页面跳转 213 实例177 上传图片预览 214 实例178 去掉下拉选项的边框 215 实例179 修改单属性为弹出窗口 216 实例180 单输入单元的文字设置 217 实例181 单输入单元单击删除 218 实例182 单文本输入的移动选择 219 实例183 通过下拉列选择头像 220 3.5 CSS+DIV页面布局 222 实例184 应用CSS+DIV实现无边框窗口 222 实例185 设置格样式 224 实例186 设置图片的样式 225 实例187 滚动条样式 226 实例188 设置单样式 227 实例189 设置超链接的样式 229 实例190 设置body的样式 230 实例191 模拟的进度条 231 实例192

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhibo_lv

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值