</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"));