Mysql 性能优化方法汇总

1、添加索引

2、合适的数据类型

尽可能小、尽可能定长、尽可能使用整数;

尽量使用对应的数据类型。比如,不要用字符串类型保存时间,用整型保存IP。

选择更小的数据类型。能用TinyInt不用Int。

3、当只要一行数据时使用 LIMIT 1

当你查询表的有些时候,你已经知道结果只会有一条结果,但因为你可能需要去fetch游标,或是你也许会去检查返回的记录数。

在这种情况下,加上 LIMIT 1 可以增加性能。这样一样,MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据。

4、避免查询无关的行

5、count操作注意

count只能统计不为null的列,所以统计总的行数使用count(*)。

6、group by

按照标识列分组效率高,分组结果不宜出现分组列之外的列。

7、Limit分页优化

利用表的覆盖索引来加速分页查询。

8、INNER JOIN 而不是 WHERE

使用INNER JOIN 而不是WHERE来创建连接(多表查询)

一些SQL开发人员更喜欢使用WHERE来做join,比如:​

SELECT Customers.CustomerID, Customers.Name, Sales.LastSaleDate FROM Customers, Sales WHERE Customers.CustomerID = Sales.CustomerID

这个类型where实际上创建时笛卡尔连接。 在笛卡尔连接中,所有可能的组合都会被创建出来。在上面的例子中,如果有1000顾客和1000条销售记录,这个查询会先产生1000000个结果,然后通过正确的 CustomerID过滤出1000条记录。 这是一种低效利用数据库资源,数据库多做100倍的工作。 在大型数据库中,笛卡尔连接是一个大问题,对两个大表的笛卡尔积会创建数10亿或万亿的记录。

为了避免创建笛卡尔积,应该使用INNER JOIN :​

SELECT Customers.CustomerID, Customers.Name, Sales.LastSaleDate FROM Customers INNER JOIN Sales ON Customers.CustomerID = Sales.CustomerID

这样数据库就只产生等于CustomerID 的1000条目标结果。

有些数据库系统会识别出 WHERE连接并自动转换为 INNER JOIN。在这些数据库系统中,WHERE 连接与INNER JOIN 就没有性能差异。但是, INNER JOIN 是所有数据库都能识别的,因此DBA会建议在你的环境中使用它。

9、一条SQL语句插入多条数据

如:

INSERT INTO insert_table (datetime, uid, content, type) 
VALUES ('0', 'userid_0', 'content_0', 0), ('1', 'userid_1', 'content_1', 1);

合并后日志量(MySQL的binlog和innodb的事务日志)减少了,降低日志刷盘的数据量和频率,从而提高效率,同时也能减少SQL语句解析的次数,减少网络传输的IO。

当然合并条数不要太多,要适量,防止阻塞数据库。同时SQL语句是有长度限制,在进行数据合并在同一SQL中务必不能超过SQL长度限制,通过max_allowed_packet配置可以修改,默认是1M,测试时修改为8M。

10、数据有序插入

数据有序的插入是指插入记录在主键上是有序排列,dateid:

INSERT INTO insert_table (dateid, uid, content, type) VALUES ('0', 'userid_0', 'content_0', 0);
INSERT INTO insert_table (dateid, uid, content, type) VALUES ('1', 'userid_1', 'content_1', 1);
INSERT INTO insert_table (dateid, uid, content, type) VALUES ('2', 'userid_2', 'content_2',2);

由于数据库插入时,需要维护索引数据,无序的记录会增大维护索引的成本。我们可以参照 innodb 使用的 B+tree 索引,如果每次插入记录都在索引的最后面,索引的定位效率很高,并且对索引调整较小;如果插入的记录在索引中间,需要 B+tree 进行分裂合并等处理,会消耗比较多计算资源,并且插入记录的索引定位效率会下降,数据量较大时会有频繁的磁盘操作。  建表时最好以自增id为主键就解决了这个问题。

11、count() 替换

有时候某些业务场景并不需要完全精确的COUNT值,可以用近似值来代替,EXPLAIN 出来的行数就是一个不错的近似值,而且执行EXPLAIN并不需要真正地去执行查询,所以成本非常低。

实例:

[SQL]select count(name) from aaa;
受影响的行: 0
时间: 2.957s

结果:
428396

[SQL]EXPLAIN select count(name) from aaa;
受影响的行: 0
时间: 0.038s

结果:
1  SIMPLE  aaa  index  name_idx  767  413996  Using index

通常来说,执行 COUNT() 都需要扫描大量的行才能获取到精确的数据,因此很难优化,MySQL层面还能做得也就只有覆盖索引了。

其他解决方法,只有从架构层面解决了,比如添加汇总表,或者使用 redis 这样的外部缓存系统。

12、关联查询优化:


在大数据场景下,表与表之间通过一个冗余字段来关联,要比直接使用JOIN有更好的性能。如果确实需要使用关联查询的情况下,需要特别注意的是:

1、确保ON和USING字句中的列上有索引。在创建索引的时候就要考虑到关联的顺序。当表A和表B用列c关联的时候,如果优化器关联的顺序是A、B,那么就不需要在A表的对应列上创建索引。

2、没有用到的索引会带来额外的负担,一般来说,除非有其他理由,只需要在关联顺序中的第二张表的相应列上创建索引。

3、确保任何的GROUP BY和ORDER BY中的表达式只涉及到一个表中的列,这样MySQL才有可能使用索引来优化。

13、union 优化:

MySQL处理UNION的策略是先创建临时表,然后再把各个查询结果插入到临时表中,最后再来做查询。因此很多优化策略在UNION查询中都没有办法很好的时候。经常需要手动将WHERE、LIMIT、ORDER BY等字句“下推”到各个子查询中,以便优化器可以充分利用这些条件先优化。

除非确实需要服务器去重,否则就一定要使用UNION ALL,如果没有ALL关键字,MySQL会给临时表加上DISTINCT选项,这会导致整个临时表的数据做唯一性检查,这样做的代价非常高。当然即使使用ALL关键字,MySQL总是将结果放入临时表,然后再读出,再返回给客户端。虽然很多时候没有这个必要,比如有时候可以直接把每个子查询的结果返回给客户端。

14、大表 alter  table:

大表ALTER TABLE非常耗时,MySQL执行大部分修改表结果操作的方法是用新的结构创建一个张空表,从旧表中查出所有的数据插入新表,然后再删除旧表。

尤其当内存不足而表又很大,而且还有很大索引的情况下,耗时更久。当然有一些奇淫技巧可以解决这个问题,有兴趣可自行查阅。

比如:大表更改默认值使用alter table不重建表,直接修改.frm

在MySQL中执行很大部分的修改动作,都需要重建一个表,然后把数据放进去,最后删除旧的表!有时候要是有索引的列上进行大批且频繁的表的时候会导致系统的性能严重下降,这里可以在修改SQL上做部分调整,减轻相关的构建结构带来的系统压力问题!

例如 在修改一个表的默认值为8的时候,常规做法为:

alter table  modes modify column  dept tinyint(3) not null default 8;

这里通过一些show status分析出,每次都要做大量的句柄的读取和插入操作,类似于从新构建了一张新表的样式,这样在一些大表,上千万的数据量会出现瓶颈问题!

这里我们需要灵活知道mysql的相关默认值实际是放在相关的表结构.frm文件中;我们可以不经过数据层,可以直接调整!上述的modify column会导致相关的表进行拷贝操作,不利于系统的正常稳定运行,可以使用下面方式;

alter table modes alter column dept set default 8;

这里只是更改了相关的frm文件,没有改动表,因此速度很快的即可完成;

总结:在此我们可以注意到相关的alter table后面跟不同形式的命令,可以对数据产生了不同程度的影响,这里还有一个change column操作也是不一样的!

15、千万不要 ORDER BY RAND()

想打乱返回的数据行?随机挑一个数据?真不知道谁发明了这种用法,但很多新手很喜欢这样用。但你确不了解这样做有多么可怕的性能问题。

如果你真的想把返回的数据行打乱了,你有N种方法可以达到这个目的。这样使用只让你的数据库的性能呈指数级的下降。这里的问题是:MySQL会不得 不去执行RAND()函数(很耗CPU时间),而且这是为了每一行记录去记行,然后再对其排序。就算是你用了Limit 1也无济于事(因为要排序)

16、避免 SELECT *

从数据库里读出越多的数据,那么查询就会变得越慢。并且,如果你的数据库服务器和WEB服务器是两台独立的服务器的话,这还会增加网络传输的负载。使用具体字段,在索引覆盖的情况下还可以避免回表。

所以,你应该养成一个需要什么就取什么的好的习惯。

// 不推荐
$r = mysql_query("SELECT * FROM user WHERE user_id = 1");
$$d = mysql_fetch_assoc($$r);
echo "Welcome {$d['username']}";

// 推荐
$r = mysql_query("SELECT username FROM user WHERE user_id = 1");
$$d = mysql_fetch_assoc($$r);
echo "Welcome {$d['username']}";

17、永远为每张表设置一个ID

我们应该为数据库里的每张表都设置一个ID做为其主键,而且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动增加的AUTO_INCREMENT标志。

就算是你 users 表有一个主键叫 “email”的字段,你也别让它成为主键。使用 VARCHAR 类型来当主键会使用得性能下降。另外,在你的程序中,你应该使用表的ID来构造你的数据结构。

而且,在MySQL数据引擎下,还有一些操作需要使用主键,在这些情况下,主键的性能和设置变得非常重要,比如,集群,分区……

在这里,只有一个情况是例外,那就是“关联表”的“外键”,也就是说,这个表的主键,通过若干个别的表的主键构成。我们把这个情况叫做“外键”。比 如:有一个“学生表”有学生的ID,有一个“课程表”有课程ID,那么,“成绩表”就是“关联表”了,其关联了学生表和课程表,在成绩表中,学生ID和课 程ID叫“外键”其共同组成主键。

18、尽可能的使用 NOT NULL

除非你有一个很特别的原因去使用 NULL 值,你应该总是让你的字段保持 NOT NULL。这看起来好像有点争议,请往下看。

首先,问问你自己“Empty”和“NULL”有多大的区别(如果是INT,那就是0和NULL)?如果你觉得它们之间没有什么区别,那么你就不要使用NULL。(你知道吗?在 Oracle 里,NULL 和 Empty 的字符串是一样的!)

不要以为 NULL 不需要空间,其需要额外的空间,并且,在你进行比较的时候,你的程序会更复杂。 当然,这里并不是说你就不能使用NULL了,现实情况是很复杂的,依然会有些情况下,你需要使用NULL值。

下面摘自MySQL自己的文档:

NULL columns require additional space in the row to record whether their values are NULL. For MyISAM tables, each NULL column takes one bit extra, rounded up to the nearest byte.”

19、选择正确的存储引擎

在 MySQL 中有两个存储引擎 MyISAM 和 InnoDB,每个引擎都有利有弊。

MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*) 这类的计算是超快无比的。

InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。

老版本中mysql默认搜索引擎是MyISAM, 现在已经都改为innodb。

20、对表进行水平划分

如果一个表的记录数太多了,比如上千万条,而且需要经常检索,那么我们就有必要化整为零了。如果我拆成100个表,那么每个表只有10万条记录。当然这 需要数据在逻辑上可以划分。一个好的划分依据,有利于程序的简单实现,也可以充分利用水平分表的优势。比如系统界面上只提供按月查询的功能,那么把表按月 拆分成12个,每个查询只查询一个表就够了。如果非要按照地域来分,即使把表拆的再小,查询还是要联合所有表来查,还不如不拆了。所以一个好的拆分依据是 最重要的。

水平分表,能够降低单表的数据量,一定程度上可以缓解查询性能瓶颈。但本质上这些表还保存在同一个库中,所以库级别还是会有IO瓶颈。所以,一般不建议采用这种做法。

21、对表进行垂直划分

有些表记录数并不多,可能也就2、3万条,但是字段却很长,表占用空间很大,检索表时需要执行大量I/O,严重降低了性能。这个时候需要把大的字段拆分到另一个表,并且该表与原表是一对一的关系。(降低了每张表的文件大小)

实例:文章信息表就可以拆分成两张表,一张记录文章信息如作者、栏目、发表日期、关键词等,另一张表记录 文章内容,摘要等。利用id进行对应。因为在大多数情况下只需要搜索第一张表即可,只有在文章展示页才需要访问两张表,如此可以提高搜索性能!

22、设置主从同步,读写分离

MySQL主从同步的作用:

1、可以作为一种备份机制,相当于热备份(在从备份,避免备份期间影响主服务器服务)

2、可以用来做读写分离,均衡数据库负载(主写从读)

3、当主服务器出现问题时,可以切换到从服务器。

23、水平分库分表

水平分库分表与上面讲到的水平分表的思想相同,唯一不同的就是将这些拆分出来的表保存在不同的数据库中。这也是很多大型互联网公司所选择的做法。

某种意义上来讲,有些系统中使用的“冷热数据分离”(将一些使用较少的历史数据迁移到其他的数据库中。而在业务功能上,通常默认只提供热点数据的查询),也是类似的实践。在高并发和海量数据的场景下,分库分表能够有效缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源的瓶颈。当然,投入的硬件成本也会更高。同时,这也会带来一些复杂的技术问题和挑战(例如:跨分片的复杂查询,跨分片事务等)

24、垂直分库

垂直分库在“微服务”盛行的今天已经非常普及了。基本的思路就是按照业务模块来划分出不同的数据库,而不是像早期一样将所有的数据表都放到同一个数据库中。

25、表命名问题

数据库表名创建时使用小写英文字母加下划线的形式,不要出现大写字母,防止大小写敏感问题(数据库、字段创建最好也遵守这一规则,保持统一)

引用链接

[1] 利用表的覆盖索引来加速分页查询。: https://fengzhongmuou.feishu.cn/wiki/U8dnwJ2yVilIP8knZh2ceSbmnMd