Du solltest vielleicht versuchen, auf der rechten Seite deiner Expression auch ein lower() unterzubringen. Ansonsten wird PostgreSQL feststellen, das links einmal Werte mit lower() abgefragt werden, rechts jedoch nicht. Das könnte genügen, um den zugehörigen Index aussen vor zu lassen.
Bleiben wir beim Thema Kosten:
Gegeben sei eine Tabelle, in der so viele Daten sind, das sie ungefähr einen Block der Datenbank auf der Festplatte füllen.
Testen wir mal, ob der Index auf dem Primary Key verwendet wird.
postgres=# EXPLAIN SELECT id, inhalt FROM planer_test WHERE id=1;
QUERY PLAN
------------------------------------------------------------
Seq Scan on planer_test (cost=0.00..1.06 rows=1 width=18)
Filter: (id = 1)
(2 rows)
Offensichtlich nicht. Zu sehen sind Kosten in Höhe von geschätzten 1.06 Plattenzugriffen. Das wäre einmal den Block mit den Daten aus einem zufälligen Block (random read) von der Platte lesen plus etwas Kosten drumherum zum Auswerten.
Gut, erzwingen wir die Nutzung des Index:
postgres=# SET enable_seqscan=0;
SET
postgres=# EXPLAIN SELECT id FROM planer_test WHERE id=1;
QUERY PLAN
--------------------------------------------------------------------------------------
Index Scan using planer_test_id_key on planer_test (cost=0.00..3.01 rows=1 width=4)
Index Cond: (id = 1)
(2 rows)
Zu sehen sind Kosten von geschätzten 3.01 Plattenzugriffen. Das wäre einmal das Lesen des Index (random read), dann das Umpositionieren des Kopfes und das Lesen des Blocks mit den Daten (noch einmal random read).
An dem Beispiel kann man sehr gut sehen, warum PostgreSQL trotz vorhandenem Index einen sequentiellen Scan wählt: die geschätzten Gesamtkosten der Anfrage sind mit Nutzung des Index 300% höher als ohne. Da die Daten sowieso gelesen werden müssen, kann das Lesen des Index auch entfallen. Die In-Memory Operationen zum Durchsuchen des gelesenen Blocks sind wesentlich billiger als das mehrfache Lesen von der Festplatte, um genau zu wissen, wo im Block man suchen muss.