数据库中使用索引进行查询语句或DML 操作的优化是一个通常的手段,通过添加索引可以加快这些操作的速度。通常在没有DBA 的场景下,开发人员对于索引的添加可能会造成一些负面的影响,这是值得注意的。下面来讲讲在POSTGRESQL 中索引添加和使用中的一些负面的影响。
过度的使用索引会造成什么问题,对数据库系统有什么负面影响,我们将从以下的几个方面来进行阐述。
一、 索引添加后,表操作中的DML操作成本提高降低表在数据吞吐的速度
这个问题在很多数据库中都会存在这个问题,添加的索引过多,导致数据表操作时DML操作异常的缓慢,首先表和索引之间是一个原子操作的关系,对每个行操作所对应的索引都需要进行数据的插入和更新。
在POSTGRESQL 中对比其他的数据库产品还会有一个特性必须被谈起,hot update, 这本来是POSTGRESQL 通过自身独有的MVCC 的方式来对于索引数据在UPDATE 后,进行更新的一种方式,初衷是避免更新行数据时阻塞其他的事务,但这样的操作有利有弊,对于以下的一些操作我们需要注意:
1 频繁的更新的业务,尤其对于数据库行快速的更新,这就导致POSTGRESQL 需要频繁的更新行数据和相关的索引,这样操作会导致更多的HOT UPDATE 的操作
2 OLAP 或类操作在访问被更新的索引的情况下,会导致查询的性能有一定的下降,在访问时需要等待更新索引的操作 完成。
3 更大量的数据插入,UPDATE 以及数据高频的查询,这三者混合起来会造成HOT UPDATE 的工作有延后的情况,这样会导致性能进一步的下降。
二、太多的索引与更新操作产生也分割的问题
这个问题实际上在其他的数据库也是存在的,基于POSTGRESQL MVCC的原理,当一个更新的操作导致他所在的页面无法承载更新后的数据后,那么POSTGRESQL 会产生分割页面的方式,将数据页写入新的页面,而页面的分割操作会产生一定的性能影响,对于IO
三、更多的索引会导致索引利用率降低,需要更多的内存缓冲的问题
在索引的使用中,索引必然是加载到内存中的,而一些非标准的索引,尤其是本来一个索引 包含2个字段可以解决的问题,而由于人为的原因,这个索引本身通过更多的字段来进行建立,这些无用的字段会同时加载到POSTGRESQL的内存中,降低数据库缓冲的利用率,同时基于这些冗余的字段在索引中,导致需要更多的内存来保存这些索引的数据库,变相提高了索引使用的成本。
四、太多的索引导致消耗更多的存储和IOPS
索引建立的过多,或单个索引中无用的字段过多,这些数据都是需要进行存储的,我们经常在分析索引的时候发现有的索引可能是这个表本身一般的容量,或更多,而有的表本身可能50个G,但这个 表上的索引们占用了300个G,这样的情况在有些项目是非常常见的,除了对于存储的消耗,另外一个部分就是这些操作中,索引太多导致IOPS 的提高,更多的索引会也会导致 vacuum 的成本增高,其中vacuum 执行中,耗时较多的是在索引的操作,对于数据表本身的操作并不十分耗时。
五、更多的索引导致WAL 日志产生的成本增高
基于POSTGRESQL WAL 产生的原理对于INDEX 在wal中也会产生基于索引的WAL记录,这个设计对于数据库崩溃后的恢复有相关的作用。但更多的索引导致产生WAL 的性能消耗较大成功更高。
六、更多的索引导致vacuum 和 autovacuum 的性能消耗更大
在前面我们提到了更新后导致的页分割等问题,同时基于POSTGRESQL的MVCC 原理,在数据更新和删除操作后,会有基于回收的vacuum 和 autovacuum 的操作,索引也是需要进行相关的回收操作,以及索引与数据之间重新指向的问题。更多的索引导致,vacuum 和 autovacuum操作复杂度提高,操作的时间加长,同时IOPS 在操作时会产生更大的压力。
怎么产生索引的问题,这也是我们需要思考的问题,一般产生不适宜的索引的问题,主要的根本原因是建立索引时,只是针对某个SQL进行的索引添加,而随着语句的增加,这样的点状的工作模式不能适应复杂的SQL,更多有组织的全局性的工作方法更有助于减少上述问题。
下面的SQL 可以查询无用的索引,通过查询获得自数据库运行后,从未使用过的索引。
SELECT
pg_stat_user_indexes.indexrelid::regclass AS index_name,
pg_class_relname(pg_stat_user_indexes.indexrelid) AS table_name,
pg_size_pretty(pg_relation_size(pg_stat_user_indexes.indexrelid)) AS index_size,
idx_scan AS number_of_scans,
idx_tup_read AS rows_read,
idx_tup_fetch AS rows_fetched,
indisvalid AS is_valid,
indexdef
FROM pg_stat_user_indexes
JOIN pg_index ON pg_index.indexrelid = pg_stat_user_indexes.indexrelid
WHERE NOT idx_scan > 0
AND pg_index.indisunique IS false;