老叶观点:MySQL开发规范之我见

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
简介: 老叶观点:MySQL开发规范之我见

大多数MySQL规范在网上也都能找得到相关的分享,在这里要分享的是老叶个人认为比较重要的,或者容易被忽视的,以及容易被混淆的一些地方。


1、默认使用InnoDB引擎

【老叶观点】已多次呼吁过了,InnoDB适用于几乎99%的MySQL应用场景,而且在MySQL 5.7的系统表都改成InnoDB了,还有什么理由再死守MyISAM呢。


此外,频繁读写的InnoDB表,一定要使用具有自增/顺序特征的整型作为显式主键


当然了,也不是说MyISAM就一无是处,比如老叶之前就把MyISAM用于临时导数据数据(把数据导入MyISAM,一番处理后再入到InnoDB表)、或者一些特殊的数据统计类场景用MyISAM(大数据量下MyISAM全表顺序读取比InnoDB有明显优势)可能比较合适。前提是,你得非常清楚MyISAM引擎的优势在哪里。


参考】:[MySQL FAQ]系列 — 为什么InnoDB表要建议用自增列做主键



2、InnoDB表行记录物理长度不超过8KB

【老叶观点】InnoDB的data page默认是16KB,基于B+Tree的特点,一个data page中需要至少存储2条记录。因此,当实际存储长度超过8KB(尤其是TEXT/BLOB列)的大列(large column)时会引起“page-overflow存储”,类似ORACLE中的“行迁移”。


因此,如果必须使用大列(尤其是TEXT/BLOB类型)且读写频繁的话,则最好把这些列拆分到子表中,不要和主表放在一起存储。如果不太频繁,可以考虑继续保留在主表中。


当然了,如果将 innodb_page_size 选项修改成 8KB,那么行记录物理长度建议不超过4KB。


参考】:[MySQL优化案例]系列 — 优化InnoDB表BLOB列的存储效率



3、是否使用表分区(partition)

【老叶观点】在一些使用表分区后明显可以提升性能或者运维便利性的场景下,还是建议使用表分区。


比如老叶就在zabbix的数据库采用TokuDB引擎的前提下,又根据时间维度使用了分区。这样的好处是保证zabbix日常应用不受到影响前提下,方便管理员例行删除过去数据,只需要删除相应分区即可,不需再执行一个非常慢的DELETE而影响整体性能。


当然了,使用表分区可能不利于物理扩展,比如大数据量下想要做哈希水平拆分,这个就见仁见智了,如果你的业务场景下使用表分区更有好处,就放心大胆的用吧。该进行拆分就用拆分方案,不要继续抱着表分区方案不放。


参考:迁移Zabbix数据库到TokuDB



4、是否使用存储过程、触发器

【老叶观点】在一些合适的场景下,用存储过程、触发器也完全没问题。


我们以前就是利用存储完成游戏业务逻辑处理,性能上不是问题,而且一旦需求有变更,只需修改存储过程,变更代价很低。我们还利用触发器维护一个频繁更新的表,对这个表的所有变更都将部分字段同步更新到另一个表中(类似物化视图的变相实现),也不存在性能问题。


有同行认为存储过程和触发器的应用可能会导致发生锁等待、死锁时排查问题上的困惑。嗯,这个是有这个可能性,不过如果真发生时,根据当时现场记录的SQL反查对应的存储过程或触发器,应该也不是难事,只不过要求DBA对线上业务环境更要了然于胸了。


总的来说,不要把MySQL的存储过程和触发器视为洪水猛兽,用好的话,没有问题的,真遇到问题了再优化也不迟。另外,MySQL因为没有物化视图,因此视图能不用就尽量少用吧。



5、选择合适的类型

【老叶观点】除了常见的建议外,还有其他几个要点:


5.1、用INT UNSIGNED存储IPV4地址,用INET_ATON()、INET_NTOA()进行转换,基本上没必要使用CHAR(15)来存储。


5.2、枚举类型可以使用ENUM,ENUM的内部存储机制是采用TINYINT或SMALLINT(并非CHAR/VARCHAR),性能一点都不差,记住千万别用CHAR/VARCHAR 来存储枚举数据。


5.3、还个早前一直在传播的“常识性误导”,建议用TIMESTAMP取代DATETIME。其实从5.6开始,建议优先选择DATETIME存储日期时间,因为它的可用范围比TIMESTAMP更大,物理存储上仅比TIMESTAMP多1个字节,整体性能上的损失并不大。


5.4、所有字段定义中,默认都加上NOT NULL约束,除非必须为NULL(但我也想不出来什么场景下必须要在数据库中存储NULL值,可以用0来表示)。在对该字段进行COUNT()统计时,统计结果更准确(值为NULL的不会被COUNT统计进去),或者执行 WHERE column IS NULL 检索时,也可以快速返回结果。


5.5、杜绝直接 SELECT * 读取全部字段,当表中存在 TEXT/BLOB 大列的时候就会是灾难了。可能本来不需要读取这些列,但因为偷懒写成 SELECT * 导致内存buffer pool被这些“垃圾”数据把真正需要缓冲起来的热点数据给洗出去了。

相应地,在写INSERT时,也要写上相对应的字段列表。

要求在SQL中写清楚每个字段的重要意义还在于,当业务需要表DDL发生更新后,如果不写清楚字段,可能会导致旧业务代码不可用,这个就折腾大发了。



6、关于索引

【老叶观点】除了常见的建议外,还有几个要点:

6.1、超过20个长度的字符串列,最好创建前缀索引而非整列索引(例如:ALTER TABLE t1 ADD INDEX(user(20))),可以有效提高索引利用率,不过它的缺点是对这个列排序时用不到前缀索引。前缀索引的长度可以基于对该字段的统计得出,一般略大于平均长度一点就可以了。


6.2、定期用 pt-duplicate-key-checker 工具检查并删除冗余的索引。比如 index idx1(a, b) 索引已经涵盖了 index idx2(a),就可以删除 idx2 索引了。


6.3、有多字段联合索引时,WHERE中过滤条件的字段顺序无需和索引一致,但如果有排序、分组则就必须一致了。


比如有联合索引 idx1(a, b, c),那么下面的SQL都可以完整用到索引

  • SELECT ... WHERE b = ? AND c = ? AND a = ?;  --注意到,WHERE中字段顺序并没有和索引字段顺序一致

  • SELECT ... WHERE b = ? AND a = ? AND c = ?;

  • SELECT ... WHERE a = ? AND b IN (?, ?) AND c = ?;
  • SELECT ... WHERE a = ? AND b = ? ORDER BY c;

  • SELECT ... WHERE a = ? AND b IN (?, ?) ORDER BY c;
  • SELECT ... WHERE a = ? ORDER BY b, c;
  • SELECT ... ORDER BY a, b, c;  -- 可利用联合索引完成排序


而下面几个SQL则只能用到部分索引

  • SELECT ... WHERE b = ? AND a = ?;   -- 只能用到 (a, b) 部分

  • SELECT ... WHERE a IN (?, ?) AND b = ?;   -- 只能用到 (a, b) 部分
  • SELECT ... WHERE a = ? AND c = ?;   -- 只能用到 (a) 部分
  • SELECT ... WHERE a = ? AND b IN (?, ?);    -- 只能用到 (a, b) 部分
  • SELECT ... WHERE (a BETWEEN ? AND ?) AND b = ?;   -- 只能用到 (a) 部分,注意BETWEEN和IN的区别
  • SELECT ... WHERE a = ? AND (b BETWEEN ? AND ?) AND c = ?;    -- 只能用到 (a, b) 部分



下面的几个SQL完全用不到该索引

  • SELECT ... WHERE b = ?;

  • SELECT ... WHERE b = ? AND c = ?;
  • SELECT ... WHERE b = ? AND c = ?;
  • SELECT ... ORDER BY b;
  • SELECT ... ORDER BY b, a;


从上面的几个例子就能看的出来,以往强调的WHERE条件字段顺序要和索引顺序一致才能使用索引的 “常识性误导 无需严格遵守。


此外,有些时候查询优化器指定的索引或执行计划可能并不是最优的,可以手工指定最优索引,或者修改session级的 optimizer_switch 选项,关闭某些导致效果反而更差的特性(比如index merge通常是好事,但也遇到过用上index merge后反而更差的,这时候要么强制指定其中一个索引,要么可以临时关闭 index merge 特性)。



7、其他
7.1、哪怕是基于索引的条件过滤,如果优化器意识到总共需要扫描的数据量超过30%时(ORACLE里貌似是20%,MySQL目前是30%,没准以后会调整),就会直接改变执行计划为全表扫描,不再使用索引。


7.2、多表JOIN时,要把过滤性最大(不一定是数据量最小哦,而是只加了WHERE条件后过滤性最大的那个)的表选为驱动表。此外,如果JOIN之后有排序,排序字段一定要属于驱动表,才能利用驱动表上的索引完成排序。


7.3、绝大多数情况下,排序的大家通常要来的更高,因此如果看到执行计划中有 Using filesort,优先创建排序索引吧。


7.4、利用 pt-query-digest 定期分析slow query log,并结合 Box Anemometer 构建slow query log分析及优化系统。


参考】:[MySQL FAQ]系列 — EXPLAIN结果中哪些信息要引起关注



备注:若无特别说明,以上规范建议适用于MySQL 5.6及之前的版本。5.7及之后的版本可能会有些变化,个别规范建议需要相应调整。


特别感谢周彦伟,对本文中的一些观点做了深入详细的探讨和完善。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
4月前
|
存储 关系型数据库 MySQL
阿里巴巴MYSQL 开发规范
阿里巴巴MYSQL 开发规范
268 0
|
6月前
|
SQL 关系型数据库 MySQL
【JavaEE进阶】 数据库连接池与MySQL企业开发规范
【JavaEE进阶】 数据库连接池与MySQL企业开发规范
|
存储 SQL 缓存
MySQL 开发规范
MySQL 开发规范
862 0
|
数据采集 缓存 Oracle
老叶倡议:MySQL压力测试基准值
老叶倡议:MySQL压力测试基准值
105 0
老叶倡议:MySQL压力测试基准值
|
存储 SQL 监控
老叶观点:MySQL开发规范之我见(更新版)
老叶观点:MySQL开发规范之我见(更新版)
159 0
|
存储 SQL 监控
老叶观点:MySQL开发规范之我见
老叶观点:MySQL开发规范之我见
131 0
|
存储 SQL 监控
MySQL开发规范之我见
MySQL开发规范之我见
134 0
|
2天前
|
SQL 关系型数据库 MySQL
go语言数据库中mysql驱动安装
【11月更文挑战第2天】
14 4
|
26天前
|
存储 关系型数据库 MySQL
Mysql(4)—数据库索引
数据库索引是用于提高数据检索效率的数据结构,类似于书籍中的索引。它允许用户快速找到数据,而无需扫描整个表。MySQL中的索引可以显著提升查询速度,使数据库操作更加高效。索引的发展经历了从无索引、简单索引到B-树、哈希索引、位图索引、全文索引等多个阶段。
58 3
Mysql(4)—数据库索引
|
11天前
|
关系型数据库 MySQL Linux
在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置 MySQL 服务、登录设置等。
本文介绍了在 CentOS 7 中通过编译源码方式安装 MySQL 数据库的详细步骤,包括准备工作、下载源码、编译安装、配置 MySQL 服务、登录设置等。同时,文章还对比了编译源码安装与使用 RPM 包安装的优缺点,帮助读者根据需求选择最合适的方法。通过具体案例,展示了编译源码安装的灵活性和定制性。
51 2
下一篇
无影云桌面