MySQL 数据库规范
1. 规范背景与目的
MySQL 数据库与 Oracle、 SQL Server 等数据库相比,有其内核上的优势与劣势。
- 优势:
- 开源: MySQL是一个开源数据库,意味着它是免费的,并且用户可以访问和修改源代码。
- 易于使用: MySQL以易用性著称,安装和配置相对简单。
- 高性能: 对于读操作密集型的应用,MySQL表现出色,尤其是在使用InnoDB存储引擎时。
- 可扩展性: MySQL支持分布式数据库,允许系统随着数据量的增长而扩展。
- 成本低: 由于它是开源的,因此对于小企业和个人项目来说,成本较低。
- 劣势:
- 事务处理: 虽然InnoDB存储引擎提供了事务支持,但与Oracle和SQL Server相比,MySQL在复杂的事务处理和高并发写操作方面可能不如它们。
- 高级功能: 与Oracle和SQL Server相比,MySQL可能缺乏一些高级功能,如高级复制、存储过程和触发器的某些特性。
- 企业支持: 虽然MySQL有社区支持,但Oracle和SQL Server提供更全面的企业级支持。
我们在使用 MySQL 数据库的时候需要遵循一定规范,扬长避短。本规范旨在帮助或指导 RD(Research and Development 研发)、QA(Quality Assurance 质量保证)、OP(Operations 运维) 等技术人员做出适合线上业务的数据库设计。在数据库变更和处理流程、数据库表设计、SQL 编写等方面予以规范,从而为公司业务系统稳定、健康地运行提供保障。
2. 设计规范
2.1 数据库设计
- 以下所有规范会按照 【高危】、【强制】、【推荐】 【参考】 四个级别进行标注,遵守优先级从高到低。
- 对于不满足 【高危】 和 【强制】 两个级别的设计,会强制打回要求修改。
- 推荐
MySql8.0之后的版本。
2.1.1 一般命名规则
- 【强制】使用小写(c#应用系统表字段除外,其余库名、表名应小写),有助于提高打字速度,避免因大小写敏感而导致的错误。
- 【强制】没有空格,单词之间使用下划线:
_代替。(c#应用系统表字段除外) - 【强制】名称中没有数字,只有英文字母。
- 【强制】有效的可理解的名称。
- 【强制】名称应该是自我解释的。
- 【强制】名称不应超过 32 个字符。
- 【强制】避免使用前缀。
- 【推荐】表的命名最好是加上“业务名称_表的作用”。 正例:health_user / trade_config
2.1.2 库
- 【强制】全部遵守以上 2.1.1 一般命名规则。
- 【强制】使用单数,符合表达习惯。
- 【强制】库名与应用名称尽量一致。如 health
- 【强制】一般分库名称命名格式是
库名_编号,编号从 0 开始递增,比如health_001,以时间进行分库的名称格式是库名_时间。
- 【强制】一般分库名称命名格式是
- 【强制】创建数据库时必须显式指定字符集
Character set,并且字符集只能是 utf8 或者 utf8mb4 ,推荐utf8mb4。 创建数据库SQL举例:
- 【强制】创建数据库时必须显式指定字符集
create database db_name default character set utf8mb4;
utf8、utf8mb4 区别:
- utf8:
- utf8是MySQL中早期对UTF-8编码的实现,它使用最多3个字节来表示一个字符。
- 然而,utf8在MySQL中并不完全符合UTF-8的标准,因为它不兼容UTF-8的4字节字符,例如一些较新的表情符号(emoji)和一些不常用的汉字。
- utf8是MySQL的默认字符集之一,但由于其不完整性,它可能不适用于需要存储所有UTF-8字符的应用。
- utf8mb4:
- utf8mb4是MySQL中对UTF-8编码的完整实现,它使用1到4个字节来表示一个字符。
- utf8mb4可以表示所有UTF-8字符,包括那些需要4个字节的字符。
- 由于utf8mb4能够存储任何UTF-8字符,它被认为是更加健壮和兼容的字符集,特别是对于国际化和多语言应用。
utf8mb4是utf8的超集,它能够存储任何utf8能存储的字符,以及更多,包括表情符号和其他多字节字符。utf8mb4相比于utf8需要更多的存储空间,因为它可能>使用更多的字节来存储每个字符。这可能会对性能和存储需求产生影响。但是,随着存储成本的降低和性能的提升,这种影响已经大大减少,因此 【推荐使用utf8mb4】 以确保最大程度的兼容性和完整性。
- 【强制】创建数据库时必须显式指定排序方式
Collation,推荐utf8mb4_0900_ai_ci
- 【强制】创建数据库时必须显式指定排序方式
utf8mb4_0900_ai_ci、utf8mb4_general_ci区别:
- utf8mb4_0900_ai_ci:
- 这个校对规则是MySQL 8.0及以后版本中引入的,它基于Unicode 9.0的排序算法。
- ai 表示“accent-insensitive”,即不区分重音。这意味着重音字符在排序时会视为相同。
- ci 表示“case-insensitive”,即不区分大小写。这意味着大写和小写字母在排序时会视为相同。
- 这个校对规则试图提供一个更加“智能”的排序方式,它考虑了不同语言的排序规则,并尝试提供更加自然的排序顺序。
- utf8mb4_general_ci:
- 这个校对规则是一个旧的校对规则,它使用一个简化的比较算法,通常基于ISO 8859-1字符集的排序规则。
- general 表示它是一个通用的校对规则,适用于大多数语言。
- ci 表示不区分大小写。
- 这个校对规则的排序结果可能不如utf8mb4_0900_ai_ci自然,特别是在处理某些语言的特殊字符时。
总的来说,utf8mb4_0900_ai_ci 提供了一个更加现代和全面的排序规则,它更加符合国际化和多语言环境的需求。如果你的应用程序需要处理多种语言的数据,>并且需要自然的排序顺序,【那么使用utf8mb4_0900_ai_ci可能是一个更好的选择。】 然而,如果你已经有一个使用utf8mb4_general_ci的系统,并且不希望改变现有的排序行为,那么可能需要继续使用这个校对规则。
2.1.3 表
- 【强制】全部遵守以上 2.1.1 一般命名规则。
- 【强制】使用单数,符合表达习惯。
- 【强制】相关模块的表名与表名之间尽量体现 join 的关系,如
user表和user_login表。
- 【强制】相关模块的表名与表名之间尽量体现 join 的关系,如
- 【强制】创建表时必须显式指定字符集为 utf8 或 utf8mb4。
- 【强制】创建表时必须显式指定表存储引擎类型,如无特殊需求,一律为 InnoDB。当需要使用除 InnoDB/MyISAM/Memory 以外的存储引擎时,必须通过 DBA 审核才能在生产环境中使用。因为 InnoDB 表支持事务、行锁、宕机恢复、MVCC 等关系型数据库重要特性,为业界使用最多的 MySQL 存储引擎。而这是其它大多数存储引擎不具备的,因此首推 InnoDB。
- 【强制】建表必须有注释
comment。
- 【强制】建表必须有注释
- 【强制】关于主键:
- (1) 命名为
id,类型必为unsigned bigint,且为auto_increment,单表时自增、步长为1; - (2) 标识表里每一行主体的字段不要设为主键,推荐设为其它字段如
user_id,order_id等,并建立unique key索引。因为如果设为主键且主键值为随机插入,则会导致 InnoDB 内部 page 分裂和大量随机 I/O,性能下降。
- 【强制】每个表必须要有 创建人
creator,创建时间create_time,修改人modifier,修改时间modify_time,是否删除is_deleted五个字段,便于排查问题。
- 【强制】每个表必须要有 创建人
- 【推荐】表中所有字段必须都是
NOT NULL属性,业务可以根据需要定义DEFAULT值。因为使用NULL值会存在每一行都会占用额外存储空间、数据迁移容易出错、聚合函数计算结果偏差等问题。
- 【推荐】表中所有字段必须都是
- 【推荐】推荐对表里的
blob、text等大字段,垂直拆分到其它表里,仅在需要读这些对象的时候才去 select。用主键来对应,避免影响其它字段索 引效率。
- 【推荐】推荐对表里的
- 【推荐】字段允许适当冗余,以提高查询性能,但必须考虑数据一致。冗余字段应遵循:
- 不是频繁修改的字段。
- 不是 varchar 超长字段,更不能是 text 字段。
例如:商品类目名称使用频率高,字段长度短,名称基本一成不变,可在相关联的表中冗余存 储类目名称,避免关联查询。
- 【强制】中间表用于保留中间结果集,名称必须以
tmp_开头。备份表用于备份或抓取源表快照,名称必须以bak_开头。中间表和备份表定期清理。
- 【强制】中间表用于保留中间结果集,名称必须以
- 【强制】对于超过 100W 行的大表进行
alter table,必须经过 DBA 审核,并在业务低峰期执行。因为alter table会产生表锁,期间阻塞对于该表的所有写入,对于业务可能会产生极大影响。
- 【强制】对于超过 100W 行的大表进行
- 【推荐】单表行数超过 500 万行或者单表容量超过 2GB,才推荐进行分库分表。 说明:如果预计2年后的数据量根本达不到这个级别,请不要在创建表时就分库分表。
2.1.4 字段
- 【强制】遵守以上全部2.1.1 一般命名规则。
- 【推荐】尽可能选择短的或一两个单词。
- 【强制】避免使用保留字作为字段名称:
order,date,name是数据库的保留字,避免使用它。可以为这些名称添加前缀使其易于理解,如user_name,signup_date等。
- 【强制】避免使用保留字作为字段名称:
- 【强制】避免使用与表名相同的字段名,这会在编写查询时造成混淆。
- 【推荐】避免使用缩写或基于首字母缩写词的名称。
2.1.5 类型规范
- 【强制】表中的自增列(
auto_increment属性),推荐使用bigint unsigned类型。因为无符号int存储范围为0~4,294,967,295(不到 43 亿),溢出后会导致报错。 - 【推荐】业务中选择性很少的状态
status、类型type等字段推荐使用tinyint unsigned,类型节省存储空间,禁止使用枚举类型,注释必须清晰地说明每个枚举的含义,以及是否多选等 - 【强制】表示boolean类型的都使用TINYINT(1),因为mysql本身是没有boolean类型的,在自动生成代码的时候,DO对象的字段就是boolean类型,例如 is_delete;其余所有时候都使用TINYINT(4)
TINYINT(4),这个括号里面的数值并不是表示使用多大空间存储,而是最大显示宽度,并且只有字段指定zerofill时有用,没有zerofill,(m)就是无用的,例如id BIGINT ZEROFILL NOT NULL,所以建表时就使用默认就好了,不需要加括号了,除非有特殊需求,例如TINYINT(1)代表boolean类型。 TINYINT(1),TINYINT(4)都是存储一个字节,并不会因为括号里的数字改变。例如TINYINT(4)存储22则会显示0022,因为最大宽度为4,达不到的情况下用0来补充。
- 【推荐】Blob 和 Text 类型所存储的数据量大,删除和修改操作容易在数 据表里产生大量的碎片,避免使用 Blob 或 Text 类型
不推荐使用
blob,text等类型。它们都比较浪费硬盘和内存空间。在加载表数据时,会读取大字段到内存里从而浪费内存空间,影响系统性能。推荐和 PM、RD 沟通,是否真的需要这么大字段。InnoDB 中当一行记录超过 8098 字节时,会将该记录中选取最长的一个字段将其 768 字节放在原始 page 里,该字段余下内容放在overflow-page里。不幸的是在compact行格式下,原始page和overflow-page都会加载。
- 【强制】小数类型(分数/金额等)为 decimal,禁止使用 float 和 double。
说明:float 和 double 在存储的时候,存在精度损失的问题,很可能在值的比较时,得到不 正确的结果。如果存储的数据范围超过 decimal 的范围,建议将数据拆成整数和小数分开存储。
【强制】如果存储的字符串长度几乎相等,使用 char 定长字符串类型。
【强制】varchar 是可变长字符串,不预先分配存储空间,长度不要超过 5000,如果存储长 度大于此值,定义字段类型为 text,独立出来一张表,用主键来对应,避免影响其它字段索 引效率。
【推荐】文本数据尽量用
varchar存储。因为varchar是变长存储,比char更省空间。MySQL server 层规定一行所有文本最多存 65535 字节,因此在 utf8 字符集下最多存 21844 个字符,超过会自动转换为mediumtext字段。而text在 utf8 字符集下最多存 21844 个字符,mediumtext最多存 2^24/3 个字符,longtext最多存 2^32 个字符。一般推荐用varchar类型,字符数不要超过 2700。【强制】时间字段使用时间日期类型,不要使用字符串类型存储,日期使用DATE类型,年使用YEAR类型,日期时间使用DATETIME
【参考】合适的字符存储长度,不但节约数据库表空间、节约索引存储,更重要的是提升检索速度。
类型(同义词) 存储长度(BYTES) 最小值(SIGNED/UNSIGNED) 最大值(SIGNED/UNSIGNED) 整形数字 TINYINT 1 -128/0 127/255 SMALLINT 2 -32,768/0 32767/65,535 MEDIUMINT 3 -8,388,608/0 8388607/16,777,215/ INT(INTEGER) 4 -2,14,7483,648/0 2147483647/4,294,967,295/ BIGINT 8 -2^63/0 2^63-1/2^64-1 小数支持 FLOAT[(M[,D])] 4 or 8 - DOUBLE[(M[,D])]
(REAL, DOUBLE PRECISION)8 - 时间类型 DATETIME 8 1001-01-01 00:00:00 9999-12-31 23:59:59 DATE 3 1001-01-01 9999-12-31 TIME 3 00:00:00 23:59:59 YEAR 1 1001 9999 TIMESTAMP 4 1970-01-01 00:00:00
2.1.6 索引规范
- 【强制】InnoDB 表必须主键为
bigint unsigned NOT NULL AUTO_INCREMENT,且主键值禁止被更新。 - 【强制】业务上具有唯一特性的字段,即使是多个字段的组合,也必须建成唯一索引。
不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明 显的;另外,即使在应用层做了非常完善的校验控制,只要没有唯一索引,根据墨菲定律,必 然有脏数据产生。
- 【推荐】主键的名称以
pk_开头,唯一键以uk_开头,普通索引以idx_开头,一律使用小写格式,以表名/字段的名称或缩写作为后缀。
说明:pk_ 即 primary key;uk_ 即 unique key;idx_ 即 index 的简称。
- 【强制】超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时, 保证被关联的字段需要有索引。
即使双表 join 也要注意表索引、SQL 性能。
- 【强制】InnoDB 和 MyISAM 存储引擎表,索引类型必须为
BTREE;MEMORY 表可以根据需要选择HASH或者BTREE类型索引。 - 【强制】单列过多重复的值,如枚举、状态、类型等类似的列禁止建立索引。
- 【强制】防止因字段类型不同造成的隐式转换,导致索引失效。
- 【推荐】利用覆盖索引来进行查询操作,避免回表。
说明:如果一本书需要知道第11章是什么标题,会翻开第11章对应的那一页吗?目录浏览一下就好,这个目录就是起到覆盖索引的作用。 正例:能够建立索引的种类:主键索引、唯一索引、普通索引,而覆盖索引是一种查询的效果,用explain的结果,extra列会出现:using index。
- 【推荐】单个表上的索引个数不能超过 7 个。
- 【强制】超过三个表禁止 join。需要 join 的字段,数据类型必须绝对一致;多表关联查询时, 保证被关联的字段需要有索引。
即使双表 join 也要注意表索引、SQL 性能。
- 【推荐】在建立索引时,多考虑建立联合索引,并把区分度最高的字段放在最前面。如列
user_id的区分度可由select count(distinct user_id)计算出来。 - 【推荐】建表或加索引时,保证表里互相不存在冗余索引。对于 MySQL 来说,如果表里已经存在
key(a, b),则key(a)为冗余索引,需要删除。 - 【推荐】建组合索引的时候,区分度最高的在最左边。
正例:如果 where a=? and b=? ,a 列的几乎接近于唯一值,那么只需要单建 idx_a 索引即 可。 说明:存在非等号和等号混合判断条件时,在建索引时,请把等号条件的列前置。如:where a>? and b=? 那么即使 a 的区分度更高,也必须把 b 放在索引的最前列。
- 【推荐】如果有 order by 的场景,请注意利用索引的有序性。order by 最后的字段是组合索引的一部分,并且放在索引组合顺序的最后,避免出现 file_sort 的情况,影响查询性能。
正例:where a=? and b=? order by c; 索引:a_b_c 反例:索引中有范围查找,那么索引有序性无法利用,如:WHERE a>10 ORDER BY b; 索引 a_b 无法排序。
- 【推荐】利用延迟关联或者子查询优化超多分页场景。深翻页问题
说明:MySQL并不是跳过 offset 行,而是取 offset+N 行,然后返回放弃前 offset 行,返回 N 行,那当 offset 特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行 SQL 改写。
例如:先快速定位需要获取的 id 段,然后再关联:
- SELECT a.* FROM 表 1 a, (select id from 表 1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
- 【推荐】SQL 性能优化的目标:至少要达到 range 级别,要求是 ref 级别,如果可以是 consts 最好。
说明:
- consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
- ref 指的是使用普通的索引(normal index)。
- range 对索引进行范围检索。
- 反例:explain 表的结果,type=index,索引物理文件全扫描,速度非常慢,这个 index 级 别比较 range 还低,与全表扫描是小巫见大巫。
- 总结
• 索引占磁盘空间,不要重复的索引,尽量短
• 只给常用的查询条件加索引
• 过滤性高的列建索引,取值范围固定的列不建索引
• 唯一的记录添加唯一索引
• 频繁更新的列不要建索引
• 不要对索引列运算
• 同样过滤效果下,保持索引长度最小
• 合理利用组合索引,注意索引字段先后顺序
• 多列组合索引,过滤性高的字段最前
• order by 字段建立索引,避免 filesort
• 组合索引,不同的排序顺序不能使用索引
• <>!=无法使用索引
2.1.7 分库分表、分区表
- 【强制】分区表的分区字段(
partition-key)必须有索引,或者是组合索引的首列。 - 【强制】单个分区表中的分区(包括子分区)个数不能超过 1024。
- 【强制】上线前 RD 或者 DBA 必须指定分区表的创建、清理策略。
- 【强制】访问分区表的 SQL 必须包含分区键。
- 【推荐】单个分区文件不超过 2G,总大小不超过 50G。推荐总分区数不超过 20 个。
- 【强制】对于分区表执行
alter table操作,必须在业务低峰期执行。 - 【强制】采用分库策略的,库的数量不能超过 1024。
- 【强制】采用分表策略的,表的数量不能超过 4096。
- 【推荐】单个分表不超过 500W 行,ibd 文件大小不超过 2G,这样才能让数据分布式变得性能更佳。
- 【推荐】水平分表尽量用取模方式,日志、报表类数据推荐采用日期进行分表。
2.1.8 字符集
- 【强制】数据库本身库、表、列所有字符集必须保持一致,为
utf8或utf8mb4。 - 【强制】前端程序字符集或者环境变量中的字符集,与数据库、表的字符集必须一致,统一为
utf8。
2.1.9 程序层 DAO 设计推荐
- 【推荐】新的代码不要用 model,推荐使用手动拼 SQL + 绑定变量传入参数的方式。因为 model 虽然可以使用面向对象的方式操作 db,但是其使用不当很容易造成生成的 SQL 非常复杂,且 model 层自己做的强制类型转换性能较差,最终导致数据库性能下降。
- 【推荐】前端程序连接 MySQL 或者 Redis,必须要有连接超时和失败重连机制,且失败重试必须有间隔时间。
- 【推荐】前端程序报错里尽量能够提示 MySQL 或 Redis 原生态的报错信息,便于排查错误。
- 【推荐】对于有连接池的前端程序,必须根据业务需要配置初始、最小、最大连接数,超时时间以及连接回收机制,否则会耗尽数据库连接资源,造成线上事故。
- 【推荐】对于
log或history类型的表,随时间增长容易越来越大,因此上线前 RD 或者 DBA 必须建立表数据清理或归档方案。 - 【推荐】在应用程序设计阶段,RD 必须考虑并规避数据库中主从延迟对于业务的影响。尽量避免从库短时延迟(20 秒以内)对业务造成影响,推荐强制一致性的读开启事务走主库,或更新后过一段时间再去读从库。
- 【推荐】多个并发业务逻辑访问同一块数据(InnoDB 表)时,会在数据库端产生行锁甚至表锁导致并发下降,因此推荐更新类 SQL 尽量基于主键去更新。
- 【推荐】业务逻辑之间加锁顺序尽量保持一致,否则会导致死锁。
- 【推荐】对于单表读写比大于 10:1 的数据行或单个列,可以将热点数据放在缓存里(如 Memcached 或 Redis),加快访问速度,降低 MySQL 压力。
2.1.10 一个规范的建表语句示例
- 一个较为规范的建表语句为:
create table user ( `id` bigint(11) not null auto_increment, `user_id` bigint(11) not null comment '用户 ID', `username` varchar(45) not null comment '登录名', `email` varchar(30) not null comment '邮箱', `nickname` varchar(45) not null comment '昵称', `avatar` int(11) not null comment '头像', `birthday` date not null comment '生日', `gender` tinyint(4) default '0' comment '性别', `intro` varchar(150) default null comment '简介', `resume_url` varchar(300) not null comment '简历存放地址', `register_ip` int not null comment '用户注册时的源 IP', `review_status` tinyint not null comment '审核状态,1-通过,2-审核中,3-未通过,4-尚未提交审核', `creator` varchar(50) not null comment '创建人', `create_time` datetime not null comment '创建的时间', `modifier` varchar(50) null comment '修改人', `modify_time` datetime null comment '修改的时间', `is_deleted` tinyint(1) default 0 comment '是否删除', primary key (`id`), unique key `idx_user_id` (`user_id`), key `idx_username`(`username`), key `idx_create_time`(`create_time`, `review_status`) ) engine = InnoDB default charset = utf8 comment = '用户基本信息';
2.2 SQL规范
2.2.1 DML 语句
- 【强制】不要使用 count(列名)或 count(常量)来替代 count(),count()是 SQL92 定义的 标准统计行数的语法,跟数据库无关,跟 NULL 和非 NULL 无关。
count(*)会统计值为 NULL 的行(所有的列为 NULL 也会统计),而 count(列名)不会统计此列为 NULL 值的行。
- 【强制】count(distinct col) 计算该列除 NULL 之外的不重复行数,
count(distinct col1, col2) 如果其中一列全为NULL,那么即使另一列有不同的值,也返回为0。 -【强制】当某一列col的值全是 NULL 时,count(col)的返回结果为 0,但 sum(col)的返回结果为 NULL,因此使用 sum()时需注意 NPE(Null Pointer Exception,这里指的的应用程序的NEP不是数据的NPE,数据库没有NPE问题,NULL对于数据库来说是一个正常的值) 问题。 正例:可以使用如下方式来避免sum的NPE问题:SELECT IF(ISNULL(SUM(g)),0,SUM(g)) FROM table;
- 【强制】使用 ISNULL()来判断是否为 NULL 值。 说明:NULL 与任何值的直接比较都为 NULL。
- NULL<>NULL的返回结果是NULL,而不是false。
- NULL=NULL的返回结果是NULL,而不是true。
- NULL<>1的返回结果是NULL,而不是true。
- 【强制】 在代码中写分页查询逻辑时,若 count 为 0 应直接返回,避免执行后面的分页语句。
- 【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
说明:以学生和成绩的关系为例,学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为 级联更新。外键与级联更新适用于单机低并发,不适合分布式、高并发集群;级联更新是强阻 塞,存在数据库更新风暴的风险;外键影响数据库的插入速度。
- 【强制】数据订正时,删除和修改记录时,要先 select,避免出现误删除,确认无误才能执行更新语句。也就是所谓的先查后改/删
- 【强制】禁止使用存储过程,存储过程难以调试和扩展,更没有移植性,版本控制困难,测试困难。
- 【推荐】in操作能避免则避免,若实在避免不了,需要仔细评估 in 后边的集合元素数量,控 制在 1000 个之内。
- 【推荐】不要写一个大而全的数据更新接口。传入为 POJO 类,不管是不是自己的目标更新字 段,都进行 update table set c1=value1,c2=value2,c3=value3; 这是不对的。执行 SQL 时,不要更新无改动的字段,一是易出错;二是效率低;三是增加 binlog 存储。
- 【参考】TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少,但 TRUNCATE 无事务且不触发trigger,有可能造成事故,故不建议在开发代码中使用此语句。 说明:TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同。
- 【推荐】select 语句必须指定具体字段名称,禁止写成
*。因为select *会将不该读的数据也从 MySQL 里读出来,造成网卡压力。 - 【强制】insert 语句指定具体字段名称,不要写成
insert into t1 values(…),道理同上。 - 【推荐】
insert into … values(xx),(xx),(xx)…,这里 xx 的值不要超过 5000 个。值过多虽然上线很快,但会引起主从同步延迟。 - 【推荐】select 语句不要使用
union,推荐使用union all,并且union子句个数限制在 5 个以内。因为union all不需要去重,节省数据库资源,提高性能。 - 【强制】写入和事务发往主库,只读 SQL 发往从库。
- 【强制】where 条件里等号左右字段类型必须一致,否则无法利用索引。
- 【推荐】索引列不要使用函数或表达式,否则无法利用索引。如
where length(name) = 'admin'或where user_id + 2 = 10023。 - 【推荐】分页查询,当
limit起点较高时,可先用过滤条件进行过滤。如select a, b, c from t1 limit 10000, 20;优化为:select a, b, c from t1 where id > 10000 limit 20;。 - 总结
提示
• 能够快速缩小结果集的 WHERE 条件写在前面,如果有恒量条 件,也尽量放在前面 ,例如 where 1=1
• 避免使用 GROUP BY、DISTINCT 等语句的使用,避免联表查 询和子查询 • 能够使用索引的字段尽量进行有效的合理排列
• 针对索引字段使用 >, >=, =, <, <=, IF NULL 和 BETWEEN 将会 使用索引,如果对某个索引字段进行 LIKE 查询,使用 LIKE ‘%abc%’ 不能使用索引,使用 LIKE ‘abc%’ >将能够使用索引
• 如果在 SQL 里使用了 MySQL部分自带函数,索引将失效 • 避免直接使用 select *,只取需要的字段,增加使用覆盖索引使用的可能
• 对于大数据量的查询,尽量避免在 SQL 语句中使用 order by 字 句 • 连表查询的情况下,要确保关联条件的数据类型一致,避免嵌 套子查询
• 对于连续的数值,使用 between 代替 in
• where 语句中尽量不要使用 CASE 条件
• 当只要一行数据时使用 LIMIT 1
2.2.2 多表连接
- 【强制】禁止在业务的更新类 SQL 语句中使用 join,比如
update t1 join t2 …。 - 【推荐】不推荐使用子查询,推荐将子查询 SQL 拆开结合程序多次查询,或使用 join 来代替子查询。
- 【推荐】线上环境,多表 join 不要超过 3 个表。
- 【推荐】多表连接查询推荐使用别名,且 select 列表中要用别名引用字段,数据库.表格式,如
select a from db1.table1 alias1 where …。 - 【推荐】在多表 join 中,尽量选取结果集较小的表作为驱动表,来 join 其它表。
2.2.3 事务
- 【推荐】批量操作数据时,需要控制事务处理间隔时间,进行必要的 sleep,一般推荐值 1-3 秒。
- 【强制】程序设计必须考虑“数据库事务隔离级别”带来的影响,包括脏读、不可重复读和幻读。线上推荐事务隔离级别为
repeatable-read。 - 【推荐】事务里包含 SQL 不超过 5 个(支付业务除外)。因为过长的事务会导致锁数据较久,MySQL 内部缓存、连接消耗过多等雪崩问题。
- 【推荐】事务里更新语句尽量基于主键或
unique key,如update … where id = XX;,否则会产生间隙锁,内部扩大锁定范围,导致系统性能下降,产生死锁。 - 【推荐】尽量把一些典型外部调用移出事务,如调用 Web Service,访问文件存储等,从而避免事务过长。
- 【推荐】对于 MySQL 主从延迟严格敏感的 select 语句,请开启事务强制访问主库。
2.2.4 排序和分组
- 【推荐】减少使用
order by,和业务沟通能不排序就不排序,或将排序放到程序端去做。order by、group by、distinct这些语句较为耗费 CPU,数据库的 CPU 资源是极其宝贵的。 - 【推荐】
order by、group by、distinct这些 SQL 尽量利用索引直接检索出排序好的数据。如where a = 1 order by可以利用key(a, b)。 - 【推荐】包含了
order by、group by、distinct这些查询的语句,where 条件过滤出来的结果集请保持在 1000 行以内,否则 SQL 会比较慢。
2.2.5 线上禁止使用的 SQL 语句
- 【高危】禁用
update|delete t1 … where a = XX limit XX;这种带 limit 的更新语句。因为会导致主从不一致,导致数据错乱。推荐加上order by PK。 - 【高危】禁止使用关联子查询,如
update t1 set … where name in(select name from user where …);,效率极其低下。 - 【强制】禁用 procedure、function、trigger、views、event、外键约束。因为他们消耗数据库资源,降低数据库实例可扩展性。推荐都在程序端实现。
- 【强制】禁用
insert into … on duplicate key update …在高并发环境下,会造成主从不一致。
说明: INSERT INTO users (user_id, username, email) VALUES (1, 'john_doe', 'john@example.com') ON DUPLICATE KEY UPDATE username = VALUES(username), email = VALUES(email);
在这个例子中,我们尝试插入一个新用户。如果user_id已经存在(因为user_id是主键或有一个唯一索引),则ON DUPLICATE KEY UPDATE子句会更新该行的username和email字段。 注意,VALUES(username)和VALUES(email)是MySQL的特殊语法,它们指的是INSERT语句中对应字段的值,而不是已经存在于表中行的值。
- 【强制】禁止联表更新语句,如
update t1, t2 where t1.id = t2.id …。
