Mysql-SQLクエリの最適化(詳細)

ここに画像の説明を挿入
コストの最適化:ハードウェア>システム構成>データベーステーブル構造> SQLとインデックス。
最適化効果:ハードウェア<システム構成<データベーステーブル構造<SQLおよびインデックス。

まず第一に、私は一般的にMySQLレイヤーの最適化のための5つの原則に従います。

减少数据访问:设置合理的字段类型,启用压缩,通过索引访问等减少磁盘 IO。

返回更少的数据:只返回需要的字段和数据分页处理,减少磁盘 IO 及网络 IO。

减少交互次数:批量 DML 操作,函数存储等减少数据连接次数。

减少服务器 CPU 开销:尽量减少数据库排序操作以及全表查询,减少 CPU 内存占用。

利用更多资源:使用表分区,可以增加并行操作,更大限度利用 CPU 资源。

SQLの最適化を要約すると、次の3つのポイントがあります。

最大化利用索引。

尽可能避免全表扫描。

减少无效数据的查询。

プログラマーのためのSQLの順序

1. SELECT 
2. DISTINCT <select_list>
3. FROM <left_table>
4. <join_type> JOIN <right_table>
5. ON <join_condition>
6. WHERE <where_condition>
7. GROUP BY <group_by_list>
8. HAVING <having_condition>
9. ORDER BY <order_by_condition>
10.LIMIT <limit_number>

mysqlの場合、sqlの実行順序

FROM
<表名> # 选取表,将多个表数据通过笛卡尔积变成一个表。
ON
<筛选条件> # 对笛卡尔积的虚表进行筛选
JOIN <join, left join, right join...> 
<join表> # 指定join,用于添加数据到on之后的虚表中,例如left join会将左表的剩余数据添加到虚表中
WHERE
<where条件> # 对上述虚表进行筛选
GROUP BY
<分组条件> # 分组
<SUM()等聚合函数> # 用于having子句进行判断,在书写上这类聚合函数是写在having判断里面的
HAVING
<分组筛选> # 对分组后的结果进行聚合筛选
SELECT
<返回数据列表> # 返回的单列必须在group by子句中,聚合函数除外
DISTINCT
# 数据除重
ORDER BY
<排序条件> # 排序
LIMIT
<行数限制>

SQL最適化の特定の操作(インデックスが使用されないシナリオは避けてください):
1。テーブルにインデックスを作成し、whereおよびgroupbyで使用されるフィールドを優先します。

2. select *の使用は避けてください。役に立たないフィールドを返すと、クエリの効率が低下します。次のように:

SELECT * FROM t 

优化方式:使用具体的字段代替*,只返回使用到的字段。

3. inとnot inを使用しないようにしてください。これにより、データベースエンジンが全表スキャンのインデックスを破棄します。次のように:

SELECT * FROM t WHERE id IN (2,3)

SELECT * FROM t1 WHERE username IN (SELECT username FROM t2)

优化方式:如果是连续数值,可以用between代替。如下:

SELECT * FROM t WHERE id BETWEEN 2 AND 3

如果是子查询,可以用exists代替。如下:

SELECT * FROM t1 WHERE EXISTS (SELECT * FROM t2 WHERE t1.username = t2.username)

4.またはの使用は避けてください。これにより、データベースエンジンは全表スキャンのインデックスを破棄します。次のように:

SELECT * FROM t WHERE id = 1 OR id = 3

优化方式:可以用union代替or。如下:

SELECT * FROM t WHERE id = 1
UNION
SELECT * FROM t WHERE id = 3

(PS:如果or两边的字段是同一个,如例子中这样。貌似两种方式效率差不多,即使union扫描的是索引,or扫描的是全表)

5.フィールドの先頭でファジークエリを回避するようにしてください。これにより、データベースエンジンが全表スキャンのインデックスを破棄します。次のように:

SELECT * FROM t WHERE username LIKE '%li%'

优化方式:尽量在字段后面使用模糊查询。如下:

SELECT * FROM t WHERE username LIKE 'li%'

補足:
要件が前にファジークエリを使用することである場合:

使用 MySQL 内置函数 INSTR(str,substr)来匹配,作用类似于 Java 中的 indexOf(),查询字符串出现的角标位置。

使用 FullText 全文索引,用 match against 检索。

数据量较大的情况,建议引用 ElasticSearch、Solr,亿级数据量检索速度秒级。

当表数据量较少(几千条儿那种),别整花里胡哨的,直接用 like '%xx%'。

6、null値の判断を回避するようにしてください。これにより、データベースエンジンはインデックスを放棄し、全表スキャンを実行します。次のように:

SELECT * FROM t WHERE score IS NULL

优化方式:可以给字段添加默认值0,对0值进行判断。如下:

SELECT * FROM t WHERE score = 0

7. where条件で等号の左側で式と関数操作を実行しないようにしてください。これにより、データベースエンジンがインデックスを破棄し、全表スキャンを実行します。次のように:

SELECT * FROM t2 WHERE score/10 = 9

SELECT * FROM t2 WHERE SUBSTR(username,1,2) = 'li'

优化方式:可以将表达式、函数操作移动到等号右侧。如下:

SELECT * FROM t2 WHERE score = 10*9

SELECT * FROM t2 WHERE username LIKE 'li%'

8.データ量が多い場合は、where 1 = 1条件の使用を避けてください。通常、クエリ条件の組み立てを容易にするために、デフォルトでこの条件を使用し、データベースエンジンはインデックスを放棄して、全表スキャンを実行します。次のように:

SELECT * FROM t WHERE 1=1

优化方式:用代码拼装sql时进行判断,没where加where,有where加and。

9.クエリ条件で<>または!=を使用することはできません

使用索引列作为条件进行查询时,需要避免使用<>或者!=等判断条件。

如确实业务需要,使用到不等于符号,需要在重新评估索引建立,避免在此字段上建立索引,改由查询条件中其他索引字段代替。

10. where条件には、複合インデックスの先頭以外の列のみが含まれます

複合(ジョイント)インデックスには、key_part1、key_part2、key_part3の3つの列が含まれますが、SQLステートメントにはインデックスの前列「key_part1」は含まれません。MySQLジョイントインデックスの左端の一致原則によれば、ジョイントインデックスは使用しないでください。

select col1 from table where key_part2=1 and key_part3=2

11.暗黙的な型変換はインデックスを引き起こしません

次のSQLステートメントでは、インデックスは列型のvarcharですが、指定された値は数値であるため、暗黙的な型変換が含まれ、インデックスが正しくありません。

select col1 from table where col_varchar=123; 

12. order byの条件は、whereの条件と一致している必要があります。一致していない場合、orderbyはソートにインデックスを使用しません。

-- 不走age索引
SELECT * FROM t order by age;
 
-- 走age索引
SELECT * FROM t where age > 0 order by age;

上記のステートメントの場合、データベースの処理シーケンスは次のとおりです。

第一步:根据 where 条件和统计信息生成执行计划,得到数据。

第二步:将得到的数据排序。当执行处理数据(order by)时,数据库会先查看第一步的执行计划,看 order by 的字段是否在执行计划中利用了索引。如果是,则可以利用索引顺序而直接取得已经排好序的数据。如果不是,则重新进行排序操作。

第三步:返回排序后的数据。

当 order by 中的字段出现在 where 条件中时,才会利用索引而不再二次排序,更准确的说,order by 中的字段在执行计划中利用了索引时,不用排序操作。

この結論は、order byだけでなく、並べ替えが必要な他の操作にも有効です。group by、union、distinctなど。

13.ヒントを使用してステートメントを正しく最適化する

MySQLでは、ヒントを使用して、オプティマイザが実行中に特定のインデックスを選択または無視するように指定できます。

一般而言,处于版本变更带来的表结构索引变化,更建议避免使用 hint,而是通过 Analyze table 多收集统计信息。

但在特定场合下,指定 hint 可以排除其他索引干扰而指定更优的执行计划:

USE INDEX 在你查询语句中表名的后面,添加 USE INDEX 来提供希望 MySQL 去参考的索引列表,就可以让 MySQL 不再考虑其他可用的索引。

例子: SELECT col1 FROM table USE INDEX (mod_time, name)...

IGNORE INDEX 如果只是单纯的想让 MySQL 忽略一个或者多个索引,可以使用 IGNORE INDEX 作为 Hint。

例子: SELECT col1 FROM table IGNORE INDEX (priority) ...

FORCE INDEX 为强制 MySQL 使用一个特定的索引,可在查询中使用FORCE INDEX 作为 Hint。

例子: SELECT col1 FROM table FORCE INDEX (mod_time) ...

クエリを実行すると、データベースシステムはクエリステートメントを自動的に分析し、最適なインデックスを選択します。ただし、多くの場合、データベースシステムのクエリオプティマイザは常に最適なインデックスを使用するとは限りません。

インデックスの選択方法がわかっている場合は、FORCE INDEXを使用して、指定されたインデックスを使用するようにクエリを強制できます。

例えば:

SELECT * FROM学生FORCEINDEX(idx_class_id)WHERE class_id = 1 ORDER BY id DESC; 14.SELECT
ステートメントの他の最適化
** selectを避ける***

首先,select * 操作在任何类型数据库中都不是一个好的 SQL 编写习惯。

使用 select * 取出全部列,**会让优化器无法完成索引覆盖扫描这类优化**,会影响优化器对执行计划的选择,也会增加网络带宽消耗,更会带来额外的 I/O,内存和 CPU 消耗。

ビジネスで実際に必要な列数を提示し、select *の代わりに列名を指定することをお勧めします。

增加查询分析器解析成本。

增减字段容易与 resultMap 配置不一致。

无用字段增加网络 消耗,尤其是 text 类型的字段。

②結果が不確かな機能は避けてください。

特定针对主从复制这类业务场景。由于原理上从库复制的是主库执行的语句,使用如 now()、rand()、sysdate()、current_user() 等不确定结果的函数很容易导致主库与从库相应的数据不一致。

另外不确定值的函数,产生的 SQL 语句无法利用 query cache。

③マルチテーブル関連クエリの場合、小さいテーブルが前に、大きいテーブルが後ろになります

在 MySQL 中,执行 from 后的表关联查询是从左往右执行的(Oracle 相反),第一张表会涉及到全表扫描。

所以将小表放在前面,先扫小表,扫描快效率较高,在扫描后面的大表,或许只扫描大表的前 100 行就符合返回条件并 return 了。

例如:表 1 有 50 条数据,表 2 有 30 亿条数据;如果全表扫描表 2,你品,那就先去吃个饭再说吧是吧。

④テーブルのエイリアスを使用する

当在 SQL 语句中连接多个表时,请使用表的别名并把别名前缀于每个列名上。这样就可以减少解析的时间并减少哪些有列名歧义引起的语法错误。

⑤HAVING文をwhere文に置き換えます

避免使用 HAVING 字句,因为 HAVING 只会在检索出所有记录之后才对结果集进行过滤,而 where 则是在聚合前刷选记录,如果能通过 where 字句限制记录的数目,那就能减少这方面的开销。
HAVING 中的条件一般用于聚合函数的过滤,**除此之外**,应该将条件写在 where 字句中。

whereとhaving:グループ関数(集計関数)の違いは、whereの背後では使用できません。

⑥Where文の接続順序を調整します

MySQLは、左から右、上から下の順序を使用してwhere句を解析します。この原則に従って、より多くのデータをフィルタリングするための条件を提示し、結果セットをできるだけ早く削減する必要があります。

15.ジョイントインデックスの利点

ジョイントインデックスの利点は次のとおりです。

①経費削減

建一个联合索引(a,b,c),实际相当于建了(a)、(a,b)、(a,b,c)三个索引。


每多一个索引,都会增加写操作的开销和磁盘空间的开销。对于大量数据的表,使用联合索引会大大的减少开销!

②カバーインデックス

对联合索引(a,b,c),如果有如下 SQL 的:
SELECT a,b,c from table where a='xx' and b = 'xx';

那么 MySQL 可以直接通过遍历索引取得数据,而无需回表,这减少了很多的随机 IO 操作。


减少 IO 操作,特别是随机 IO 其实是 DBA 主要的优化策略。所以,在真正的实际应用中,覆盖索引是主要的提升性能的优化手段之一。

③高効率

索引列多,通过联合索引筛选出的数据越少。比如有 1000W 条数据的表,有如下 SQL:
select col1,col2,col3 from table where col1=1 and col2=2 and col3=3;

仮定:各条件でデータの10%を除外できると仮定します。

A:如果只有单列索引,那么通过该索引能筛选出 1000W10%=100w 条数据,然后再回表从 100W 条数据中找到符合 col2=2 and col3= 3 的数据,然后再排序,再分页,以此类推(递归)。


B:如果是(col1,col2,col3)联合索引,通过三列索引筛选出 1000W10% 10% *10%=1W,效率提升可想而知!

16.できるだけ多くのインデックスを作成する方がよいですか?答えは当然ノーです。
少量のデータを含むテーブルにインデックスを付ける必要はなく、作成すると追加のインデックスオーバーヘッドが増加します。

不经常引用的列不要建立索引,因为不常用,即使建立了索引也没有多大意义。

经常频繁更新的列不要建立索引,因为肯定会影响插入或更新的效率。

数据重复且分布平均的字段,因此他建立索引就没有太大的效果(例如性别字段,只有男女,不适合建立索引)。

数据变更需要维护索引,意味着索引越多维护成本越高。

更多的索引也需要更多的存储空间。

おすすめ

転載: blog.csdn.net/weixin_43941676/article/details/113938466