文章转载自公众号:AustinDatabases
这个系列已经写到了第9篇,上一篇讲述了索引的一些基础使用的方式,这一篇将继续这个系列,这篇还是针对短查询OLTP的查询中的一些索引的方式和一些有意思的地方进行讲述。
基于查询中的一个不能被回避的问题,就是索引使用中的覆盖索引,提到覆盖索引的这个问题,其实最大的优势就是不用回表,在查询中可以从索引直接提取数据,而不必再通过索引的标记的物理位置再回到原表在将原表的数据导入到内存。
实际上这样做的好处除了快以外被经常提到,还有一个原因我很少听说,就是节省内存。很多人可能会想,这是一回事吗?实际上是的,我们都在提覆盖索引会少进行一次I/O操作,而I/O操作中的数据去了哪里,一定是内存,那么内存中少存储一次原表的数据,是不是一个好事,当然,又快有节省内存。
那么我下次建立索引的时候直接建立覆盖索引不就好了,覆盖索引的该怎么用,实际上覆盖索引的使用是有条件的,覆盖索引也有平衡点和性价比,数据库中最重要的是空间换时间,那么覆盖索引必然会增加数据空间的使用,因为实际上你将数据多存了一份,比如明明你可以建立一个字段,你为了提高数据返回的效率,直接将数据在索引存储,那么
-
下次表中的数据在此进行数据的写入更新的时候,我们就要考虑性能问题,如果你将每个查询需要的索引都建立成覆盖索引,那么你的数据写入的性能必然是一个问题
-
数据量的问题,如果你的覆盖索引很大,这里指的是列的数量,那么你的数据库的内存一定不能小尤其是频繁被使用的覆盖索引。
set search_path to postgres_air;
explain (analyze,verbose) SELECT departure_airport,
scheduled_departure ,
scheduled_arrival
FROM flight
WHERE arrival_airport='JFK' AND departure_airport='ORD'
AND scheduled_departure BETWEEN '2020-07-03' AND '2020-07-04';
CREATE INDEX flight_depart_arr_sched_dep_inc_sched_arr
ON flight
(departure_airport,
arrival_airport,
scheduled_departure)
INCLUDE (scheduled_arrival);
对比上面的查询,可以看到覆盖索引查询的效率对比一般的简单的索引的效率上有明显的优势。
在多个表的查询中,对于数据的查询的难点在于理解查询的业务,以及数据分布的情况,并且利用这些信息,提前过滤数据。
下面有一个例子,我们针对邮件email 中的字段@前面的名字进行索引,因为大部分的后缀都是一样的。
下面就是一个前缀的查询,那么我们的索引到底咱们建立
create index idx_name_booking on postgres_air.booking (email);
create index idx_name_booking_like on postgres_air.booking (email varchar_pattern_ops);
上方是针对这个查询中的两种索引的建立的方式,但是到底哪种的索引可以被这样的查询的方式去使用?
1、我们建立idx_name_booking 后来分析查询是否可以使用索引
很明显我们的查询中在建立了第一个索引后,并未走索引。
2、我们建立 idx_name_booking_like 后,可以看到的确是走了索引,那么
实际上我们针对LIKE类的需求的索引就需要使用第二种索引的建立的方式。
create index idx_name_booking_like on postgres_air.booking (email varchar_pattern_ops);
下面的语句可以通过他来看到你当前的系统中可以支持的索引的operation
SELECT am.amname AS index_method,
opc.opcname AS opclass_name,
opc.opcintype::regtype AS indexed_type,
opc.opcdefault AS is_default
FROM pg_am am, pg_opclass opc
WHERE opc.opcmethod = am.oid
ORDER BY index_method, opclass_name;
在索引的建立中还与查询的形式和查询的条件的顺序有关,例如
SELECT last_name,
first_name,
seat
FROM boarding_pass bp
JOIN booking_leg bl USING (booking_leg_id)
JOIN flight f USING (flight_id)
JOIN booking b USING(booking_id)
JOIN passenger p USING (passenger_id)
WHERE
departure_airport='JFK'
AND scheduled_departure BETWEEN
'2020-07-10' AND '2020-07-11'
AND last_name ='JOHNSON'
order by scheduled_departure desc;
那么基于上面的查询的索引应该怎么进行建立,实际上我们有一个方法,叫缩小排除法。
这里有一个口诀,先缩小,后排序,范围大了不索引,先等于,后范围,排序字段放最后。
首先我们要了解到业务,这是一个查询某航空港的过境的人的信息的查询,其中会涉及 1 抵达的航空港的简写 2 客户的名字(相当于中国的姓),以及抵达的日期。从逻辑上可以分析出,这个查询中航空港一定不是一个好被定为的信息,而人的姓是以其分布的特性的,但具有不确定性,比如你找一个姓 ‘晶’的姓氏的,和找一个姓‘刘’ 的,这两个的体量一定是不一样的。
所以我们的查询一定以 last_name 为开头,同时辅助以日期作为辅助,但日期也不稳定,如果将日志的范围扩大,则这个索引也会失效,但基于最后的查询还是要排序。
那么我们来比较一下,我们建立索引的方式,我们建立了两个索引,查询的时间控制在160毫秒。
我们建立了第三个索引,将我们的条件中的departure_airport 添加到我们的索引中,从查询的执行情况看,比原来的查询快12毫秒。
通过上面的事例,可以看到,索引的建立与我们的查询中数据的分布是有关的,并不是看见有条件就要建立索引,索引的存在会对你数据库的更新插入,甚至是查询产生负面的影响。
所以索引不是越多越好,而是和你的数据的分布和查询的方式有关。