diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 4308128..76f8cff 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -1468,6 +1468,9 @@ GetSnapshotData(Snapshot snapshot)
 
 	snapshot->curcid = GetCurrentCommandId(false);
 
+	/* Initialise the single xid cache for this snapshot */
+	snapshot->xid_in_snapshot = InvalidTransactionId;
+
 	/*
 	 * This is a new snapshot, so set both refcounts are zero, and mark it as
 	 * not copied in persistent memory.
diff --git a/src/backend/utils/time/tqual.c b/src/backend/utils/time/tqual.c
index 24384b4..68413bd 100644
--- a/src/backend/utils/time/tqual.c
+++ b/src/backend/utils/time/tqual.c
@@ -1532,6 +1532,25 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
 		return true;
 
 	/*
+	 * If we've seen this xid last time then we use our cached knowledge
+	 * to allow a fast path out.
+	 *
+	 * When XidInMVCCSnapshot is called repeatedly in a transaction it
+	 * is usually because we're viewing the results of large transactions.
+	 * Typically there will be just one big concurrent transaction and so
+	 * its very fast to just remember that and be done. Lots of short
+	 * transactions don't cause problems because their xids quickly go
+	 * above the snapshot's xmax and get ignore before we get here.
+	 *
+	 * XXX If there were more big concurrent transactions then it would make
+	 * sense to remember a list of xids and an LRU scheme. For long lists it
+	 * would make better sense to sort the snapshot xids. Both of those
+	 * ideas have much more complex code and diminishing returns.
+	 */
+	if (TransactionIdEquals(xid, snapshot->xid_in_snapshot))
+		return true;
+
+	/*
 	 * Snapshot information is stored slightly differently in snapshots taken
 	 * during recovery.
 	 */
@@ -1552,7 +1571,10 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
 			for (j = 0; j < snapshot->subxcnt; j++)
 			{
 				if (TransactionIdEquals(xid, snapshot->subxip[j]))
+				{
+					snapshot->xid_in_snapshot = xid;
 					return true;
+				}
 			}
 
 			/* not there, fall through to search xip[] */
@@ -1574,7 +1596,10 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
 		for (i = 0; i < snapshot->xcnt; i++)
 		{
 			if (TransactionIdEquals(xid, snapshot->xip[i]))
+			{
+				snapshot->xid_in_snapshot = xid;
 				return true;
+			}
 		}
 	}
 	else
@@ -1610,7 +1635,10 @@ XidInMVCCSnapshot(TransactionId xid, Snapshot snapshot)
 		for (j = 0; j < snapshot->subxcnt; j++)
 		{
 			if (TransactionIdEquals(xid, snapshot->subxip[j]))
+			{
+				snapshot->xid_in_snapshot = xid;
 				return true;
+			}
 		}
 	}
 
diff --git a/src/include/utils/snapshot.h b/src/include/utils/snapshot.h
index e747191..605895e 100644
--- a/src/include/utils/snapshot.h
+++ b/src/include/utils/snapshot.h
@@ -56,6 +56,12 @@ typedef struct SnapshotData
 	bool		copied;			/* false if it's a static snapshot */
 
 	/*
+	 * Single transaction cache. Keep track of common xid so we can respond
+	 * quickly when we keep seeing an xid while we use this snapshot.
+	 */
+	TransactionId xid_in_snapshot;
+
+	/*
 	 * note: all ids in subxip[] are >= xmin, but we don't bother filtering
 	 * out any that are >= xmax
 	 */
