咱们开发的大部分软件,其基本业务流程都是:采集数据→将数据存储到数据库中→根据业务需求查询相应数据→对数据进行处理→传给前台展现。对整个流程进行分析,能够发现软件大部分的操做时间消耗都花在了数据库相关的IO操做上。因此对咱们的SQL语句进行优化,能够提升软件的响应性能,带来更好的用户体验。php
在开始介绍SQL优化技巧以前,先推介一款数据库管理神器Navicat,官网:https://www.navicat.com.cn/whatisnavicat
html
Navicat是一套快速、可靠和全面的数据库管理工具,专门用于简化数据库管理和下降管理成本。Navicat 的直观图形用户界面,提供简单的方法管理,设计和操做MySQL、MariaDB、SQL Server、Oracle、PostgreSQL和 SQLite的数据。mysql
在遇到Navicat以前,开发软件经常使用的数据库管理工具备:sql
(1)MySQL
数据库
phpMyAdmin,官网:https://www.phpmyadmin.net/缓存
MySQL Workbench,官网:http://dev.mysql.com/downloads/workbench/性能优化
(2)Orace编辑器
PL/SQL Developer,官网:https://www.plsqldev.com/函数
PL/SQL Developer是一个集成开发环境,由Allround Automations公司开发,专门面向Oracle数据库存储的程序单元的开发。工具
(3)SQL Server
SQL Server Management Studio 是一个集成环境,用于访问、配置、管理和开发 SQL Server 的全部组件。SQL Server Management Studio 组合了大量图形工具和丰富的脚本编辑器,使各类技术水平的开发人员和管理员都能访问 SQL Server。
前面侃了不少废话,言归正传,正式进入正题:SQL优化技巧。
①-⑤条测试中使用的SQL基于Oracle数据库。
CREATE INDEX tb1_idx ON tb1 (date_column); SELECT text_column1, date_column FROM tb1 WHERE date_column >= TO_DATE ('2015-01-01', 'YYYY-MM-DD') AND date_column < TO_DATE ('2016-01-01', 'YYYY-MM-DD');
CREATE INDEX tb1_idx ON tb1 (a, date_column); SELECT * FROM ( SELECT id, text_column1, date_column FROM tb1 WHERE a =: a ORDER BY date_column DESC ) WHERE rownum < = 1;
这条SQL语句将会按照通过索引的 Top-N 查询方式执行,它的效率跟INDEX UNIQUE SCAN是等效的。
CREATE INDEX tb1_idx ON tb1 (a, b); SELECT id, a, b FROM tb1 WHERE a =: a AND b =: b; SELECT id, a, b FROM tb1 WHERE b =: b;
创建的索引只能用于第一个查询,第二个SQL没法利用索引提升效率。
CREATE INDEX tb1_idx ON tb1 (text_column1); SELECT id, text_column1 FROM tb1 WHERE text_column1 LIKE '%TermStr%';
LIKE对应的查询字符若是是以通配符开头的,索引将没法发挥效能。也没有一个简单的方法来优化这种SQL。
CREATE INDEX tb1_idx ON tb1 (a, date_column); SELECT date_column, count(*) FROM tb1 WHERE a= :a GROUP BY date_column; SELECT date_column, count(*) FROM tb1 WHERE a = :a AND b = :b GROUP BY date_column;
上面两条查询语句,第一条可能会查出几千或者几万条记录,而第二条语句由于多了一个条件可能只查出几条或几十条记录,也许你们会认为第二条语句的效率更快。其实恰好相反,第一条语句的执行效率更快。由于第一条语句中,索引覆盖了全部查询字段,而第二个SQL中的b条件没有索引。
如下测试中使用的SQL基于MySQL数据库。
若是采用的引擎是MyISAM,能够直接执行COUNT(*)去获取行数便可。类似的,在堆表中也会将行数存储到表的元信息中。但若是引擎是InnoDB状况就会复杂一些,由于InnoDB不保存表的具体行数。能够将行数缓存起来,而后能够经过一个守护进程按期更新或者用户的某些操做致使缓存失效时,执行下面的语句:
SELECT COUNT(*) FROM test USE INDEX(PRIMARY);
个人一个测试实例:
offset(分页偏移量)很大时,像下面这样:
SELECT vendorcode, vendorname FROM dm_vendor_performance_mx_v LIMIT 10000000,20
大的分页偏移量会增长使用的数据,MySQL会将大量最终不会使用的数据加载到内存中。就算咱们假设大部分网站的用户只访问前几页数据,但少许的大的分页偏移量的请求也会对整个系统形成危害。Facebook意识到了这一点,但Facebook并无为了每秒能够处理更多的请求而去优化数据库,而是将重心放在将请求响应时间的方差变小。
按照实时性排序(最新发布的在最前面,即Id最大的在最前面),实现一个高性能的分页。
一个比较高效的方式是基于要查询的最大Id。查询下一页的语句以下,须要传入当前页面展现的最后一个Id。
SELECT id, vendorcode, perioddate, materialcode FROM dm_vendor_performance_mx_v WHERE id < 1000000 ORDER BY id DESC LIMIT 20
查询上一页的语句相似,只不过须要传入当前页的第一个Id,而且要逆序。
SELECT id, vendorcode, perioddate, materialcode FROM dm_vendor_performance_mx_v WHERE id > 1500000 ORDER BY id DESC LIMIT 20
上面的查询方式适合实现简易的分页,即不显示具体的页数导航,只显示“上一页”和“下一页”,例如博客中页脚显示“上一页”,“下一页”的按钮。但若是要实现真正的页面导航仍是很难的,下面看看另外一种方式。
若是表中的记录不多被删除、修改,还能够将记录对应的页码存储到表中,并在该列上建立合适的索引。采用这种方式,当新增一个记录的时候,须要执行下面的查询从新生成对应的页号。
SET p:= 0; UPDATE test SET page=CEIL((p:= p + 1) / $perpage) ORDER BY id DESC;
固然,也能够新增一个专用于分页的表,能够用个后台程序来维护。
UPDATE pagination T JOIN ( SELECT id, CEIL((p:= p + 1) / $perpage) page FROM test ORDER BY id )C ON C.id = T.id SET T.page = C.page;
如今想获取任意一页的元素就很简单了:
SELECT * FROM test A JOIN pagination B ON A.id=B.ID WHERE page=$offset;
SQL优化还有不少技巧,我在这里也只是班门弄斧,和资深的DBA比起来还差十万八千里。
如下是我推荐的一些SQL优化的文章:
(1)MySQL知识分享网站:http://ourmysql.com/archives/category/optimize
(2)Sql养成一个好习惯是一笔财富:http://www.cnblogs.com/MR_ke/archive/2011/05/29/2062085.html
(3)MySQL查询语句执行过程:http://shanks.leanote.com/post/MySQL%E6%9F%A5%E8%AF%A2%E8%BF%87%E7%A8%8B
(4)MySQL分页性能优化指南:http://www.codeceo.com/article/mysql-page-performance.html
(5)21条最佳MySQL性能优化:http://www.phpxs.com/post/5092/
(6)100+个MySQL调试和优化技巧:http://mp.weixin.qq.com/s?__biz=MzAwMDM2NzUxMg==&mid=2247484514&idx=1&sn=2cb4246bbf991186eb08aeacd71b2893&scene=21#wechat_redirect