</listitem>
      </varlistentry>
 
+     <varlistentry>
+      <term><option>--min-mxid-age <replaceable class="parameter">mxid_age</replaceable></option></term>
+      <listitem>
+       <para>
+        Only execute the vacuum or analyze commands on tables with a multixact
+        ID age of at least <replaceable class="parameter">mxid_age</replaceable>.
+        This setting is useful for prioritizing tables to process to prevent
+        multixact ID wraparound (see
+        <xref linkend="vacuum-for-multixact-wraparound"/>).
+       </para>
+       <para>
+        For the purposes of this option, the multixact ID age of a relation is
+        the greatest of the ages of the main relation and its associated
+        <acronym>TOAST</acronym> table, if one exists.  Since the commands
+        issued by <application>vacuumdb</application> will also process the
+        <acronym>TOAST</acronym> table for the relation if necessary, it does
+        not need to be considered separately.
+       </para>
+       <note>
+        <para>
+         This option is only available for servers running
+         <productname>PostgreSQL</productname> 9.6 and later.
+        </para>
+       </note>
+      </listitem>
+     </varlistentry>
+
+     <varlistentry>
+      <term><option>--min-xid-age <replaceable class="parameter">xid_age</replaceable></option></term>
+      <listitem>
+       <para>
+        Only execute the vacuum or analyze commands on tables with a
+        transaction ID age of at least
+        <replaceable class="parameter">xid_age</replaceable>.  This setting
+        is useful for prioritizing tables to process to prevent transaction
+        ID wraparound (see <xref linkend="vacuum-for-wraparound"/>).
+       </para>
+       <para>
+        For the purposes of this option, the transaction ID age of a relation
+        is the greatest of the ages of the main relation and its associated
+        <acronym>TOAST</acronym> table, if one exists.  Since the commands
+        issued by <application>vacuumdb</application> will also process the
+        <acronym>TOAST</acronym> table for the relation if necessary, it does
+        not need to be considered separately.
+       </para>
+       <note>
+        <para>
+         This option is only available for servers running
+         <productname>PostgreSQL</productname> 9.6 and later.
+        </para>
+       </note>
+      </listitem>
+     </varlistentry>
+
      <varlistentry>
       <term><option>-q</option></term>
       <term><option>--quiet</option></term>
 
 
 use PostgresNode;
 use TestLib;
-use Test::More tests => 38;
+use Test::More tests => 44;
 
 program_help_ok('vacuumdb');
 program_version_ok('vacuumdb');
    [qr/^.*vacuuming database "postgres"/],
    [qr/^WARNING.*cannot vacuum non-tables or special system tables/s],
    'vacuumdb with view');
+$node->command_fails(
+   [ 'vacuumdb', '--table', 'vactable', '--min-mxid-age', '0',
+     'postgres'],
+   'vacuumdb --min-mxid-age with incorrect value');
+$node->command_fails(
+   [ 'vacuumdb', '--table', 'vactable', '--min-xid-age', '0',
+     'postgres'],
+   'vacuumdb --min-xid-age with incorrect value');
+$node->issues_sql_like(
+   [ 'vacuumdb', '--table', 'vactable', '--min-mxid-age', '2147483000',
+     'postgres'],
+   qr/GREATEST.*relminmxid.*2147483000/,
+   'vacuumdb --table --min-mxid-age');
+$node->issues_sql_like(
+   [ 'vacuumdb', '--min-xid-age', '2147483001', 'postgres' ],
+   qr/GREATEST.*relfrozenxid.*2147483001/,
+   'vacuumdb --table --min-xid-age');
 
    bool        freeze;
    bool        disable_page_skipping;
    bool        skip_locked;
+   int         min_xid_age;
+   int         min_mxid_age;
 } vacuumingOptions;
 
 
        {"analyze-in-stages", no_argument, NULL, 3},
        {"disable-page-skipping", no_argument, NULL, 4},
        {"skip-locked", no_argument, NULL, 5},
+       {"min-xid-age", required_argument, NULL, 6},
+       {"min-mxid-age", required_argument, NULL, 7},
        {NULL, 0, NULL, 0}
    };
 
            case 5:
                vacopts.skip_locked = true;
                break;
+           case 6:
+               vacopts.min_xid_age = atoi(optarg);
+               if (vacopts.min_xid_age <= 0)
+               {
+                   fprintf(stderr, _("%s: minimum transaction ID age must be at least 1\n"),
+                           progname);
+                   exit(1);
+               }
+               break;
+           case 7:
+               vacopts.min_mxid_age = atoi(optarg);
+               if (vacopts.min_mxid_age <= 0)
+               {
+                   fprintf(stderr, _("%s: minimum multixact ID age must be at least 1\n"),
+                           progname);
+                   exit(1);
+               }
+               break;
            default:
                fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
                exit(1);
    bool        failed = false;
    bool        parallel = concurrentCons > 1;
    bool        tables_listed = false;
+   bool        has_where = false;
    const char *stage_commands[] = {
        "SET default_statistics_target=1; SET vacuum_cost_delay=0;",
        "SET default_statistics_target=10; RESET vacuum_cost_delay;",
        exit(1);
    }
 
+   if (vacopts->min_xid_age != 0 && PQserverVersion(conn) < 90600)
+   {
+       fprintf(stderr, _("%s: cannot use the \"%s\" option on server versions older than PostgreSQL 9.6\n"),
+               progname, "--min-xid-age");
+       exit(1);
+   }
+
+   if (vacopts->min_mxid_age != 0 && PQserverVersion(conn) < 90600)
+   {
+       fprintf(stderr, _("%s: cannot use the \"%s\" option on server versions older than PostgreSQL 9.6\n"),
+               progname, "--min-mxid-age");
+       exit(1);
+   }
+
    if (!quiet)
    {
        if (stage != ANALYZE_NO_STAGE)
    appendPQExpBuffer(&catalog_query,
                      " FROM pg_catalog.pg_class c\n"
                      " JOIN pg_catalog.pg_namespace ns"
-                     " ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n");
+                     " ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
+                     " LEFT JOIN pg_catalog.pg_class t"
+                     " ON c.reltoastrelid OPERATOR(pg_catalog.=) t.oid\n");
 
    /* Used to match the tables listed by the user */
    if (tables_listed)
     * processed in which case the user will know about it.
     */
    if (!tables_listed)
+   {
        appendPQExpBuffer(&catalog_query, " WHERE c.relkind OPERATOR(pg_catalog.=) ANY (array["
                          CppAsString2(RELKIND_RELATION) ", "
                          CppAsString2(RELKIND_MATVIEW) "])\n");
+       has_where = true;
+   }
+
+   /*
+    * For --min-xid-age and --min-mxid-age, the age of the relation is the
+    * greatest of the ages of the main relation and its associated TOAST
+    * table.  The commands generated by vacuumdb will also process the TOAST
+    * table for the relation if necessary, so it does not need to be
+    * considered separately.
+    */
+   if (vacopts->min_xid_age != 0)
+   {
+       appendPQExpBuffer(&catalog_query,
+                         " %s GREATEST(pg_catalog.age(c.relfrozenxid),"
+                         " pg_catalog.age(t.relfrozenxid)) "
+                         " OPERATOR(pg_catalog.>=) '%d'::pg_catalog.int4\n"
+                         " AND c.relfrozenxid OPERATOR(pg_catalog.!=)"
+                         " '0'::pg_catalog.xid\n",
+                         has_where ? "AND" : "WHERE", vacopts->min_xid_age);
+       has_where = true;
+   }
+
+   if (vacopts->min_mxid_age != 0)
+   {
+       appendPQExpBuffer(&catalog_query,
+                         " %s GREATEST(pg_catalog.mxid_age(c.relminmxid),"
+                         " pg_catalog.mxid_age(t.relminmxid)) OPERATOR(pg_catalog.>=)"
+                         " '%d'::pg_catalog.int4\n"
+                         " AND c.relminmxid OPERATOR(pg_catalog.!=)"
+                         " '0'::pg_catalog.xid\n",
+                         has_where ? "AND" : "WHERE", vacopts->min_mxid_age);
+       has_where = true;
+   }
 
    /*
     * Execute the catalog query.  We use the default search_path for this
    printf(_("  -f, --full                      do full vacuuming\n"));
    printf(_("  -F, --freeze                    freeze row transaction information\n"));
    printf(_("  -j, --jobs=NUM                  use this many concurrent connections to vacuum\n"));
+   printf(_("      --min-mxid-age=MXID_AGE     minimum multixact ID age of tables to vacuum\n"));
+   printf(_("      --min-xid-age=XID_AGE       minimum transaction ID age of tables to vacuum\n"));
    printf(_("  -q, --quiet                     don't write any messages\n"));
    printf(_("      --skip-locked               skip relations that cannot be immediately locked\n"));
    printf(_("  -t, --table='TABLE[(COLUMNS)]'  vacuum specific table(s) only\n"));