(Note: I'm not a deep expert in the Pg guts. This is only my understanding. You should really read the mvcc and internals sections of the manual).
Yes, initially the row with id = 1
is still in the index, despite the index's WHERE
clause excluding it because it has been updated with archived = true
. At some point down the track the index entry will be purged by VACUUM
or autovacuum, freeing the space.
The reason for this is PostgreSQL's MVCC design. Other concurrent transactions might still be able to see the old version of the row with id = 1, archived = false
- either READ COMMITTED
transactions with a long-running statement, or SERIALIZABLE
transactions that don't see changes committed after they began. If the index entry were removed when the row was updated those transactions wouldn't find the row in index scans and would produce the wrong result.
Technically what's actually happening is that the old row still exists, it's just had an xmax
set so newer transactions ignore it when they see it. After the update the index points to that row, same as before. A new row with archived = true
has been inserted with an xmin
above the old row's xmax
, so any given transaction either sees the old or new version, never both. That new row is not added to the index.
Eventually there will be no open transaction that can still see the old row, so VACUUM
will come by and remove the old row and its index entry, freeing the space for re-use.
Note that the index its self has no row visibility information. You can - and often do - have many versions of a row, each with index entries. When a transaction looks for matching index entries it reads all of the versions (unless it can tell from the visibility map that it doesn't need to) and ignores all but the one that has an xmin
and xmax
that makes it visible to that transaction.
This is one of many good reasons to ensure that autovaccum runs frequently. It reduces the number of dead index entries and dead rows you have lying around wasting space and I/O.