*** a/src/backend/executor/nodeIndexonlyscan.c
--- b/src/backend/executor/nodeIndexonlyscan.c
***************
*** 30,35 ****
--- 30,36 ----
  #include "executor/nodeIndexonlyscan.h"
  #include "executor/nodeIndexscan.h"
  #include "storage/bufmgr.h"
+ #include "storage/predicate.h"
  #include "utils/memutils.h"
  #include "utils/rel.h"
  
***************
*** 52,58 **** IndexOnlyNext(IndexOnlyScanState *node)
  	ExprContext *econtext;
  	ScanDirection direction;
  	IndexScanDesc scandesc;
! 	HeapTuple	tuple;
  	TupleTableSlot *slot;
  	ItemPointer tid;
  
--- 53,59 ----
  	ExprContext *econtext;
  	ScanDirection direction;
  	IndexScanDesc scandesc;
! 	HeapTuple	tuple = NULL;
  	TupleTableSlot *slot;
  	ItemPointer tid;
  
***************
*** 147,152 **** IndexOnlyNext(IndexOnlyScanState *node)
--- 148,165 ----
  			}
  		}
  
+ 		/*
+ 		 * Predicate locks for index-only scans must be acquired at the page
+ 		 * level when the heap is not accessed, since tuple-level predicate
+ 		 * locks need the tuple's xmin value.  If we had to visit the tuple
+ 		 * anyway, then we already have the tuple-level lock and can skip the
+ 		 * page lock.
+ 		 */
+ 		if (tuple == NULL)
+ 			PredicateLockPage(scandesc->heapRelation,
+ 							  ItemPointerGetBlockNumber(tid),
+ 							  estate->es_snapshot);
+ 
  		return slot;
  	}
  
*** /dev/null
--- b/src/test/isolation/expected/index-only-scan.out
***************
*** 0 ****
--- 1,41 ----
+ Parsed test spec with 2 sessions
+ 
+ starting permutation: rxwy1 c1 rywx2 c2
+ step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+ step c1: COMMIT;
+ step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+ step c2: COMMIT;
+ 
+ starting permutation: rxwy1 rywx2 c1 c2
+ step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+ step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+ step c1: COMMIT;
+ step c2: COMMIT;
+ ERROR:  could not serialize access due to read/write dependencies among transactions
+ 
+ starting permutation: rxwy1 rywx2 c2 c1
+ step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+ step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+ step c2: COMMIT;
+ step c1: COMMIT;
+ ERROR:  could not serialize access due to read/write dependencies among transactions
+ 
+ starting permutation: rywx2 rxwy1 c1 c2
+ step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+ step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+ step c1: COMMIT;
+ step c2: COMMIT;
+ ERROR:  could not serialize access due to read/write dependencies among transactions
+ 
+ starting permutation: rywx2 rxwy1 c2 c1
+ step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+ step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+ step c2: COMMIT;
+ step c1: COMMIT;
+ ERROR:  could not serialize access due to read/write dependencies among transactions
+ 
+ starting permutation: rywx2 c2 rxwy1 c1
+ step rywx2: DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby);
+ step c2: COMMIT;
+ step rxwy1: DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx);
+ step c1: COMMIT;
*** a/src/test/isolation/isolation_schedule
--- b/src/test/isolation/isolation_schedule
***************
*** 9,14 **** test: ri-trigger
--- 9,15 ----
  test: partial-index
  test: two-ids
  test: multiple-row-versions
+ test: index-only-scan
  test: fk-contention
  test: fk-deadlock
  test: fk-deadlock2
*** /dev/null
--- b/src/test/isolation/specs/index-only-scan.spec
***************
*** 0 ****
--- 1,46 ----
+ # index-only scan test
+ #
+ # This test tries to expose problems with the interaction between index-only
+ # scans and SSI.
+ #
+ # Any overlap between the transactions must cause a serialization failure.
+ 
+ setup
+ {
+   CREATE TABLE tabx (id int NOT NULL);
+   INSERT INTO tabx SELECT generate_series(1,10000);
+   ALTER TABLE tabx ADD PRIMARY KEY (id);
+   CREATE TABLE taby (id int NOT NULL);
+   INSERT INTO taby SELECT generate_series(1,10000);
+   ALTER TABLE taby ADD PRIMARY KEY (id);
+ }
+ setup { VACUUM FREEZE ANALYZE tabx; }
+ setup { VACUUM FREEZE ANALYZE taby; }
+ 
+ teardown
+ {
+   DROP TABLE tabx;
+   DROP TABLE taby;
+ }
+ 
+ session "s1"
+ setup
+ {
+   BEGIN ISOLATION LEVEL SERIALIZABLE;
+   SET LOCAL seq_page_cost = 0.1;
+   SET LOCAL random_page_cost = 0.1;
+   SET LOCAL cpu_tuple_cost = 0.03;
+ }
+ step "rxwy1" { DELETE FROM taby WHERE id = (SELECT min(id) FROM tabx); }
+ step "c1" { COMMIT; }
+ 
+ session "s2"
+ setup
+ {
+   BEGIN ISOLATION LEVEL SERIALIZABLE;
+   SET LOCAL seq_page_cost = 0.1;
+   SET LOCAL random_page_cost = 0.1;
+   SET LOCAL cpu_tuple_cost = 0.03;
+ }
+ step "rywx2" { DELETE FROM tabx WHERE id = (SELECT min(id) FROM taby); }
+ step "c2" { COMMIT; }
