diff --git a/contrib/pg_upgrade/pg_upgrade.c b/contrib/pg_upgrade/pg_upgrade.c
index 24d9d51e54d853f9417050ee477613c4573c3970..547ae83d499ccdaeb6cc7186887b98829adcc9a5 100644
--- a/contrib/pg_upgrade/pg_upgrade.c
+++ b/contrib/pg_upgrade/pg_upgrade.c
@@ -438,13 +438,13 @@ static void
 prepare_new_databases(void)
 {
 	/*
-	 * We set autovacuum_freeze_max_age to its maximum value so autovacuum
-	 * does not launch here and delete clog files, before the frozen xids are
-	 * set.
+	 * Before we restore anything, set frozenxids of initdb-created tables.
 	 */
-
 	set_frozenxids(false);
 
+	/*
+	 * Now restore global objects (roles and tablespaces).
+	 */
 	prep_status("Restoring global objects in the new cluster");
 
 	/*
@@ -682,14 +682,25 @@ copy_clog_xlog_xid(void)
 /*
  *	set_frozenxids()
  *
- *	We have frozen all xids, so set relfrozenxid and datfrozenxid
- *	to be the old cluster's xid counter, which we just set in the new
- *	cluster.  User-table frozenxid values will be set by pg_dumpall
- *	--binary-upgrade, but objects not set by the pg_dump must have
- *	proper frozen counters.
+ * This is called on the new cluster before we restore anything, with
+ * minmxid_only = false.  Its purpose is to ensure that all initdb-created
+ * vacuumable tables have relfrozenxid/relminmxid matching the old cluster's
+ * xid/mxid counters.  We also initialize the datfrozenxid/datminmxid of the
+ * built-in databases to match.
+ *
+ * As we create user tables later, their relfrozenxid/relminmxid fields will
+ * be restored properly by the binary-upgrade restore script.  Likewise for
+ * user-database datfrozenxid/datminmxid.  However, if we're upgrading from a
+ * pre-9.3 database, which does not store per-table or per-DB minmxid, then
+ * the relminmxid/datminmxid values filled in by the restore script will just
+ * be zeroes.
+ *
+ * Hence, with a pre-9.3 source database, a second call occurs after
+ * everything is restored, with minmxid_only = true.  This pass will
+ * initialize all tables and databases, both those made by initdb and user
+ * objects, with the desired minmxid value.  frozenxid values are left alone.
  */
-static
-void
+static void
 set_frozenxids(bool minmxid_only)
 {
 	int			dbnum;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index e67a41bb9eb1b4edcf9428ee9ebf6160797bcac2..443d9ef8b61065ec85b263b44425788d879c923a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -13284,6 +13284,13 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 * order.  That also means we have to take care about setting
 		 * attislocal correctly, plus fix up any inherited CHECK constraints.
 		 * Analogously, we set up typed tables using ALTER TABLE / OF here.
+		 *
+		 * We process foreign tables here, even though they lack heap storage,
+		 * because they can participate in inheritance relationships and we
+		 * want this stuff to be consistent across the inheritance tree.  We
+		 * exclude indexes, toast tables, sequences and matviews, even though
+		 * they have storage, because we don't support altering or dropping
+		 * columns in them, nor can they be part of inheritance trees.
 		 */
 		if (binary_upgrade && (tbinfo->relkind == RELKIND_RELATION || 
 							   tbinfo->relkind == RELKIND_FOREIGN_TABLE) )
@@ -13374,7 +13381,19 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 								  fmtId(tbinfo->dobj.name),
 								  tbinfo->reloftype);
 			}
+		}
 
+		/*
+		 * In binary_upgrade mode, arrange to restore the old relfrozenxid and
+		 * relminmxid of all vacuumable relations.  (While vacuum.c processes
+		 * TOAST tables semi-independently, here we see them only as children
+		 * of other relations; so this "if" lacks RELKIND_TOASTVALUE, and the
+		 * child toast table is handled below.)
+		 */
+		if (binary_upgrade &&
+			(tbinfo->relkind == RELKIND_RELATION ||
+			 tbinfo->relkind == RELKIND_MATVIEW))
+		{
 			appendPQExpBuffer(q, "\n-- For binary upgrade, set heap's relfrozenxid and relminmxid\n");
 			appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 							  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
@@ -13385,7 +13404,10 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 
 			if (tbinfo->toast_oid)
 			{
-				/* We preserve the toast oids, so we can use it during restore */
+				/*
+				 * The toast table will have the same OID at restore, so we
+				 * can safely target it by OID.
+				 */
 				appendPQExpBuffer(q, "\n-- For binary upgrade, set toast's relfrozenxid and relminmxid\n");
 				appendPQExpBuffer(q, "UPDATE pg_catalog.pg_class\n"
 								  "SET relfrozenxid = '%u', relminmxid = '%u'\n"
@@ -13399,7 +13421,8 @@ dumpTableSchema(Archive *fout, TableInfo *tbinfo)
 		 * In binary_upgrade mode, restore matviews' populated status by
 		 * poking pg_class directly.  This is pretty ugly, but we can't use
 		 * REFRESH MATERIALIZED VIEW since it's possible that some underlying
-		 * matview is not populated even though this matview is.
+		 * matview is not populated even though this matview is; in any case,
+		 * we want to transfer the matview's heap storage, not run REFRESH.
 		 */
 		if (binary_upgrade && tbinfo->relkind == RELKIND_MATVIEW &&
 			tbinfo->relispopulated)