More than a week ago, I deleted all the rows in a postgresql table (not via truncate, instead with delete from ...). Select count (*) reveals table rows are now 0.

When I now run queries to query disk space, I still see the table taking up space. Specifically, all the indexes still exist and are taking up space. How do I get rid of them and release disk space? Secondly, why do they remain in the first place, when I've gotten rid of all the rows?

Here's the table's detailed description in case warranted:

                                      Table "public.links_photoobjectsubscription"
     Column     |           Type           |                                 Modifiers                                  
 id             | integer                  | not null default nextval('links_photoobjectsubscription_id_seq'::regclass)
 viewer_id      | integer                  | not null
 updated_at     | timestamp with time zone | not null
 seen           | boolean                  | not null
 type_of_object | character varying(15)    | not null
 which_photo_id | integer                  | 
 which_link_id  | integer                  | 
 which_group_id | integer                  | 
 which_salat_id | integer                  | 
    "links_photoobjectsubscription_pkey" PRIMARY KEY, btree (id)
    "links_photoobjectsubscription_seen" btree (seen)
    "links_photoobjectsubscription_updated_at" btree (updated_at)
    "links_photoobjectsubscription_viewer_id" btree (viewer_id)
    "links_photoobjectsubscription_which_photo_id" btree (which_photo_id)
Foreign-key constraints:
    "links_photoobjectsubscription_viewer_id_fkey" FOREIGN KEY (viewer_id) REFERENCES auth_user(id) DEFERRABLE INITIALLY DEFERRED
    "links_photoobjectsubscription_which_photo_id_fkey" FOREIGN KEY (which_photo_id) REFERENCES links_photo(id) DEFERRABLE INITIALLY DEFERRED
    "which_group_id_photoobjectsubscription" FOREIGN KEY (which_group_id) REFERENCES links_group(id) ON DELETE CASCADE
    "which_link_id_photoobjectsubscription" FOREIGN KEY (which_link_id) REFERENCES links_link(id) ON DELETE CASCADE
    "which_salat_id_photoobjectsubscription" FOREIGN KEY (which_salat_id) REFERENCES links_salatinvite(id) ON DELETE CASCADE

1 Answer 1


From the docs on VACUUM

There are two variants of VACUUM: standard VACUUM and VACUUM FULL. VACUUM FULL can reclaim more disk space but runs much more slowly. Also, the standard form of VACUUM can run in parallel with production database operations. (Commands such as SELECT, INSERT, UPDATE, and DELETE will continue to function normally, though you will not be able to modify the definition of a table with commands such as ALTER TABLE while it is being vacuumed.) VACUUM FULL requires exclusive lock on the table it is working on, and therefore cannot be done in parallel with other use of the table. Generally, therefore, administrators should strive to use standard VACUUM and avoid VACUUM FULL.

You essentially need to issue a command to rewrite the whole table (from the same doc),

Tip: Plain VACUUM may not be satisfactory when a table contains large numbers of dead row versions as a result of massive update or delete activity. If you have such a table and you need to reclaim the excess disk space it occupies, you will need to use VACUUM FULL, or alternatively CLUSTER or one of the table-rewriting variants of ALTER TABLE. These commands rewrite an entire new copy of the table and build new indexes for it. All these options require exclusive lock. Note that they also temporarily use extra disk space approximately equal to the size of the table, since the old copies of the table and indexes can't be released until the new ones are complete.

In the same document,

Tip: If you have a table whose entire contents are deleted on a periodic basis, consider doing it with TRUNCATE rather than using DELETE followed by VACUUM. TRUNCATE removes the entire content of the table immediately, without requiring a subsequent VACUUM or VACUUM FULL to reclaim the now-unused disk space. The disadvantage is that strict MVCC semantics are violated.

And, from the docs on TRUNCATE

TRUNCATE quickly removes all rows from a set of tables. It has the same effect as an unqualified DELETE on each table, but since it does not actually scan the tables it is faster. Furthermore, it reclaims disk space immediately, rather than requiring a subsequent VACUUM operation. This is most useful on large tables.

  • Can I do Truncate <tablename>? Commented Jan 11, 2017 at 17:57
  • @HassanBaig updated, answer: yes. Commented Jan 11, 2017 at 18:01
  • Okay cool. I would have thought that with the table itself releasing diskspace, the indexes would release disk space too. Any guidance on why that didn't happen for my particular table? Commented Jan 11, 2017 at 18:03
  • 1
    Because DELETE does not acquire an explicit table lock, and without doing that it's not possible to release disk space. Transactions use DELETE and they work with other transactions reading from and deleting the same table. Conversely, TRUNCATE acquires an ACCESS EXCLUSIVE (highest level). It'll wait to get it. After it gets it nothing else can get it. Then it nukes the table and ensures no one writes to it before it comes back online. Commented Jan 11, 2017 at 18:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.