MySQL 性能
①最大数据量
抛开数据量和并发数,谈性能都是耍流氓。MySQL 没有限制单表最大记录数,它取决于操作系统对文件大小的限制。
《阿里巴巴 Java 开发手册》提出单表行数超过 500 万行或者单表容量超过 2GB,才推荐分库分表。
性能由综合因素决定,抛开业务复杂度,影响程度依次是硬件配置、MySQL 配置、数据表设计、索引优化。500 万这个值仅供参考,并非铁律。
我曾经操作过超过 4 亿行数据的单表,分页查询最新的 20 条记录耗时 0.6 秒,SQL 语句大致是:
select field_1,field_2 from table where id < #{prePageMinId} order by id desc limit 20
prePageMinId 是上一页数据记录的最小 ID。虽然当时查询速度还凑合,随着数据不断增长,有朝一日必定不堪重负。
分库分表是个周期长而风险高的大活儿,应该尽可能在当前结构上优化,比如升级硬件、迁移历史数据等等,实在没辙了再分。对分库分表感兴趣的同学可以阅读分库分表的基本思想。
②最大并发数
并发数是指同一时刻数据库能处理多少个请求,由 max_connections 和 max_user_connections 决定。
max_connections 是指 MySQL 实例的最大连接数,上限值是 16384,max_user_connections 是指每个数据库用户的最大连接数。
MySQL 会为每个连接提供缓冲区,意味着消耗更多的内存。如果连接数设置太高硬件吃不消,太低又不能充分利用硬件。
一般要求两者比值超过 10%,计算方法如下:
max_used_connections / max_connections * 100% = 3/100 *100% ≈ 3%
查看最大连接数与响应最大连接数:
show variables like '%max_connections%'; show variables like '%max_user_connections%';
在配置文件 my.cnf 中修改最大连接数:
[mysqld] max_connections = 100 max_used_connections = 20
③查询耗时 0.5 秒
建议将单次查询耗时控制在 0.5 秒以内,0.5 秒是个经验值,源于用户体验的 3 秒原则。如果用户的操作 3 秒内没有响应,将会厌烦甚至退出。
响应时间=客户端 UI 渲染耗时+网络请求耗时+应用程序处理耗时+查询数据库耗时,0.5 秒就是留给数据库 1/6 的处理时间。
④实施原则
相比 NoSQL 数据库,MySQL 是个娇气脆弱的家伙。它就像体育课上的女同学,一点纠纷就和同学闹别扭(扩容难),跑两步就气喘吁吁(容量小并发低),常常身体不适要请假(SQL 约束太多)。
如今大家都会搞点分布式,应用程序扩容比数据库要容易得多,所以实施原则是数据库少干活,应用程序多干活:
- 充分利用但不滥用索引,须知索引也消耗磁盘和 CPU。
- 不推荐使用数据库函数格式化数据,交给应用程序处理。
- 不推荐使用外键约束,用应用程序保证数据准确性。
- 写多读少的场景,不推荐使用唯一索引,用应用程序保证唯一性。
- 适当冗余字段,尝试创建中间表,用应用程序计算中间结果,用空间换时间。
- 不允许执行极度耗时的事务,配合应用程序拆分成更小的事务。
- 预估重要数据表(比如订单表)的负载和数据增长态势,提前优化。
数据表设计
①数据类型
数据类型的选择原则,更简单或者占用空间更小:
- 如果长度能够满足,整型尽量使用 tinyint、smallint、medium_int 而非 int。
- 如果字符串长度确定,采用 char 类型。
- 如果 varchar 能够满足,不采用 text 类型。
- 精度要求较高的使用 decimal 类型,也可以使用 BIGINT,比如精确两位小数就乘以 100 后保存。
- 尽量采用 timestamp 而非 datetime。
相比 datetime,timestamp 占用更少的空间,以 UTC 的格式储存自动转换时区。
②避免空值
MySQL 中字段为 NULL 时依然占用空间,会使索引、索引统计更加复杂。从 NULL 值更新到非 NULL 无法做到原地更新,容易发生索引分裂影响性能。
因此尽可能将 NULL 值用有意义的值代替,也能避免 SQL 语句里面包含 is not null 的判断。
③Text 类型优化
由于 Text 字段储存大量数据,表容量会很早涨上去,影响其他字段的查询性能。建议抽取出来放在子表里,用业务主键关联。