PageRenderTime 59ms CodeModel.GetById 28ms RepoModel.GetById 1ms app.codeStats 0ms

/src/bin/scripts/vacuumdb.c

http://github.com/postgres/postgres
C | 954 lines | 729 code | 93 blank | 132 comment | 108 complexity | 0443650ca62aa8d8f8e188baef09a668 MD5 | raw file
Possible License(s): AGPL-3.0
  1. /*-------------------------------------------------------------------------
  2. *
  3. * vacuumdb
  4. *
  5. * Portions Copyright (c) 1996-2020, PostgreSQL Global Development Group
  6. * Portions Copyright (c) 1994, Regents of the University of California
  7. *
  8. * src/bin/scripts/vacuumdb.c
  9. *
  10. *-------------------------------------------------------------------------
  11. */
  12. #include "postgres_fe.h"
  13. #include "catalog/pg_class_d.h"
  14. #include "common.h"
  15. #include "common/logging.h"
  16. #include "fe_utils/cancel.h"
  17. #include "fe_utils/connect.h"
  18. #include "fe_utils/simple_list.h"
  19. #include "fe_utils/string_utils.h"
  20. #include "scripts_parallel.h"
  21. /* vacuum options controlled by user flags */
  22. typedef struct vacuumingOptions
  23. {
  24. bool analyze_only;
  25. bool verbose;
  26. bool and_analyze;
  27. bool full;
  28. bool freeze;
  29. bool disable_page_skipping;
  30. bool skip_locked;
  31. int min_xid_age;
  32. int min_mxid_age;
  33. int parallel_workers; /* >= 0 indicates user specified the
  34. * parallel degree, otherwise -1 */
  35. } vacuumingOptions;
  36. static void vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
  37. int stage,
  38. SimpleStringList *tables,
  39. const char *host, const char *port,
  40. const char *username, enum trivalue prompt_password,
  41. int concurrentCons,
  42. const char *progname, bool echo, bool quiet);
  43. static void vacuum_all_databases(vacuumingOptions *vacopts,
  44. bool analyze_in_stages,
  45. const char *maintenance_db,
  46. const char *host, const char *port,
  47. const char *username, enum trivalue prompt_password,
  48. int concurrentCons,
  49. const char *progname, bool echo, bool quiet);
  50. static void prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
  51. vacuumingOptions *vacopts, const char *table);
  52. static void run_vacuum_command(PGconn *conn, const char *sql, bool echo,
  53. const char *table);
  54. static void help(const char *progname);
  55. /* For analyze-in-stages mode */
  56. #define ANALYZE_NO_STAGE -1
  57. #define ANALYZE_NUM_STAGES 3
  58. int
  59. main(int argc, char *argv[])
  60. {
  61. static struct option long_options[] = {
  62. {"host", required_argument, NULL, 'h'},
  63. {"port", required_argument, NULL, 'p'},
  64. {"username", required_argument, NULL, 'U'},
  65. {"no-password", no_argument, NULL, 'w'},
  66. {"password", no_argument, NULL, 'W'},
  67. {"echo", no_argument, NULL, 'e'},
  68. {"quiet", no_argument, NULL, 'q'},
  69. {"dbname", required_argument, NULL, 'd'},
  70. {"analyze", no_argument, NULL, 'z'},
  71. {"analyze-only", no_argument, NULL, 'Z'},
  72. {"freeze", no_argument, NULL, 'F'},
  73. {"all", no_argument, NULL, 'a'},
  74. {"table", required_argument, NULL, 't'},
  75. {"full", no_argument, NULL, 'f'},
  76. {"verbose", no_argument, NULL, 'v'},
  77. {"jobs", required_argument, NULL, 'j'},
  78. {"parallel", required_argument, NULL, 'P'},
  79. {"maintenance-db", required_argument, NULL, 2},
  80. {"analyze-in-stages", no_argument, NULL, 3},
  81. {"disable-page-skipping", no_argument, NULL, 4},
  82. {"skip-locked", no_argument, NULL, 5},
  83. {"min-xid-age", required_argument, NULL, 6},
  84. {"min-mxid-age", required_argument, NULL, 7},
  85. {NULL, 0, NULL, 0}
  86. };
  87. const char *progname;
  88. int optindex;
  89. int c;
  90. const char *dbname = NULL;
  91. const char *maintenance_db = NULL;
  92. char *host = NULL;
  93. char *port = NULL;
  94. char *username = NULL;
  95. enum trivalue prompt_password = TRI_DEFAULT;
  96. bool echo = false;
  97. bool quiet = false;
  98. vacuumingOptions vacopts;
  99. bool analyze_in_stages = false;
  100. bool alldb = false;
  101. SimpleStringList tables = {NULL, NULL};
  102. int concurrentCons = 1;
  103. int tbl_count = 0;
  104. /* initialize options to all false */
  105. memset(&vacopts, 0, sizeof(vacopts));
  106. vacopts.parallel_workers = -1;
  107. pg_logging_init(argv[0]);
  108. progname = get_progname(argv[0]);
  109. set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pgscripts"));
  110. handle_help_version_opts(argc, argv, "vacuumdb", help);
  111. while ((c = getopt_long(argc, argv, "h:p:U:wWeqd:zZFat:fvj:P:", long_options, &optindex)) != -1)
  112. {
  113. switch (c)
  114. {
  115. case 'h':
  116. host = pg_strdup(optarg);
  117. break;
  118. case 'p':
  119. port = pg_strdup(optarg);
  120. break;
  121. case 'U':
  122. username = pg_strdup(optarg);
  123. break;
  124. case 'w':
  125. prompt_password = TRI_NO;
  126. break;
  127. case 'W':
  128. prompt_password = TRI_YES;
  129. break;
  130. case 'e':
  131. echo = true;
  132. break;
  133. case 'q':
  134. quiet = true;
  135. break;
  136. case 'd':
  137. dbname = pg_strdup(optarg);
  138. break;
  139. case 'z':
  140. vacopts.and_analyze = true;
  141. break;
  142. case 'Z':
  143. vacopts.analyze_only = true;
  144. break;
  145. case 'F':
  146. vacopts.freeze = true;
  147. break;
  148. case 'a':
  149. alldb = true;
  150. break;
  151. case 't':
  152. {
  153. simple_string_list_append(&tables, optarg);
  154. tbl_count++;
  155. break;
  156. }
  157. case 'f':
  158. vacopts.full = true;
  159. break;
  160. case 'v':
  161. vacopts.verbose = true;
  162. break;
  163. case 'j':
  164. concurrentCons = atoi(optarg);
  165. if (concurrentCons <= 0)
  166. {
  167. pg_log_error("number of parallel jobs must be at least 1");
  168. exit(1);
  169. }
  170. break;
  171. case 'P':
  172. vacopts.parallel_workers = atoi(optarg);
  173. if (vacopts.parallel_workers < 0)
  174. {
  175. pg_log_error("parallel vacuum degree must be a non-negative integer");
  176. exit(1);
  177. }
  178. break;
  179. case 2:
  180. maintenance_db = pg_strdup(optarg);
  181. break;
  182. case 3:
  183. analyze_in_stages = vacopts.analyze_only = true;
  184. break;
  185. case 4:
  186. vacopts.disable_page_skipping = true;
  187. break;
  188. case 5:
  189. vacopts.skip_locked = true;
  190. break;
  191. case 6:
  192. vacopts.min_xid_age = atoi(optarg);
  193. if (vacopts.min_xid_age <= 0)
  194. {
  195. pg_log_error("minimum transaction ID age must be at least 1");
  196. exit(1);
  197. }
  198. break;
  199. case 7:
  200. vacopts.min_mxid_age = atoi(optarg);
  201. if (vacopts.min_mxid_age <= 0)
  202. {
  203. pg_log_error("minimum multixact ID age must be at least 1");
  204. exit(1);
  205. }
  206. break;
  207. default:
  208. fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
  209. exit(1);
  210. }
  211. }
  212. /*
  213. * Non-option argument specifies database name as long as it wasn't
  214. * already specified with -d / --dbname
  215. */
  216. if (optind < argc && dbname == NULL)
  217. {
  218. dbname = argv[optind];
  219. optind++;
  220. }
  221. if (optind < argc)
  222. {
  223. pg_log_error("too many command-line arguments (first is \"%s\")",
  224. argv[optind]);
  225. fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
  226. exit(1);
  227. }
  228. if (vacopts.analyze_only)
  229. {
  230. if (vacopts.full)
  231. {
  232. pg_log_error("cannot use the \"%s\" option when performing only analyze",
  233. "full");
  234. exit(1);
  235. }
  236. if (vacopts.freeze)
  237. {
  238. pg_log_error("cannot use the \"%s\" option when performing only analyze",
  239. "freeze");
  240. exit(1);
  241. }
  242. if (vacopts.disable_page_skipping)
  243. {
  244. pg_log_error("cannot use the \"%s\" option when performing only analyze",
  245. "disable-page-skipping");
  246. exit(1);
  247. }
  248. /* allow 'and_analyze' with 'analyze_only' */
  249. }
  250. /* Prohibit full and analyze_only options with parallel option */
  251. if (vacopts.parallel_workers >= 0)
  252. {
  253. if (vacopts.analyze_only)
  254. {
  255. pg_log_error("cannot use the \"%s\" option when performing only analyze",
  256. "parallel");
  257. exit(1);
  258. }
  259. if (vacopts.full)
  260. {
  261. pg_log_error("cannot use the \"%s\" option when performing full",
  262. "parallel");
  263. exit(1);
  264. }
  265. }
  266. setup_cancel_handler(NULL);
  267. /* Avoid opening extra connections. */
  268. if (tbl_count && (concurrentCons > tbl_count))
  269. concurrentCons = tbl_count;
  270. if (alldb)
  271. {
  272. if (dbname)
  273. {
  274. pg_log_error("cannot vacuum all databases and a specific one at the same time");
  275. exit(1);
  276. }
  277. if (tables.head != NULL)
  278. {
  279. pg_log_error("cannot vacuum specific table(s) in all databases");
  280. exit(1);
  281. }
  282. vacuum_all_databases(&vacopts,
  283. analyze_in_stages,
  284. maintenance_db,
  285. host, port, username, prompt_password,
  286. concurrentCons,
  287. progname, echo, quiet);
  288. }
  289. else
  290. {
  291. if (dbname == NULL)
  292. {
  293. if (getenv("PGDATABASE"))
  294. dbname = getenv("PGDATABASE");
  295. else if (getenv("PGUSER"))
  296. dbname = getenv("PGUSER");
  297. else
  298. dbname = get_user_name_or_exit(progname);
  299. }
  300. if (analyze_in_stages)
  301. {
  302. int stage;
  303. for (stage = 0; stage < ANALYZE_NUM_STAGES; stage++)
  304. {
  305. vacuum_one_database(dbname, &vacopts,
  306. stage,
  307. &tables,
  308. host, port, username, prompt_password,
  309. concurrentCons,
  310. progname, echo, quiet);
  311. }
  312. }
  313. else
  314. vacuum_one_database(dbname, &vacopts,
  315. ANALYZE_NO_STAGE,
  316. &tables,
  317. host, port, username, prompt_password,
  318. concurrentCons,
  319. progname, echo, quiet);
  320. }
  321. exit(0);
  322. }
  323. /*
  324. * vacuum_one_database
  325. *
  326. * Process tables in the given database. If the 'tables' list is empty,
  327. * process all tables in the database.
  328. *
  329. * Note that this function is only concerned with running exactly one stage
  330. * when in analyze-in-stages mode; caller must iterate on us if necessary.
  331. *
  332. * If concurrentCons is > 1, multiple connections are used to vacuum tables
  333. * in parallel. In this case and if the table list is empty, we first obtain
  334. * a list of tables from the database.
  335. */
  336. static void
  337. vacuum_one_database(const char *dbname, vacuumingOptions *vacopts,
  338. int stage,
  339. SimpleStringList *tables,
  340. const char *host, const char *port,
  341. const char *username, enum trivalue prompt_password,
  342. int concurrentCons,
  343. const char *progname, bool echo, bool quiet)
  344. {
  345. PQExpBufferData sql;
  346. PQExpBufferData buf;
  347. PQExpBufferData catalog_query;
  348. PGresult *res;
  349. PGconn *conn;
  350. SimpleStringListCell *cell;
  351. ParallelSlot *slots;
  352. SimpleStringList dbtables = {NULL, NULL};
  353. int i;
  354. int ntups;
  355. bool failed = false;
  356. bool parallel = concurrentCons > 1;
  357. bool tables_listed = false;
  358. bool has_where = false;
  359. const char *stage_commands[] = {
  360. "SET default_statistics_target=1; SET vacuum_cost_delay=0;",
  361. "SET default_statistics_target=10; RESET vacuum_cost_delay;",
  362. "RESET default_statistics_target;"
  363. };
  364. const char *stage_messages[] = {
  365. gettext_noop("Generating minimal optimizer statistics (1 target)"),
  366. gettext_noop("Generating medium optimizer statistics (10 targets)"),
  367. gettext_noop("Generating default (full) optimizer statistics")
  368. };
  369. Assert(stage == ANALYZE_NO_STAGE ||
  370. (stage >= 0 && stage < ANALYZE_NUM_STAGES));
  371. conn = connectDatabase(dbname, host, port, username, prompt_password,
  372. progname, echo, false, true);
  373. if (vacopts->disable_page_skipping && PQserverVersion(conn) < 90600)
  374. {
  375. PQfinish(conn);
  376. pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
  377. "disable-page-skipping", "9.6");
  378. exit(1);
  379. }
  380. if (vacopts->skip_locked && PQserverVersion(conn) < 120000)
  381. {
  382. PQfinish(conn);
  383. pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
  384. "skip-locked", "12");
  385. exit(1);
  386. }
  387. if (vacopts->min_xid_age != 0 && PQserverVersion(conn) < 90600)
  388. {
  389. pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
  390. "--min-xid-age", "9.6");
  391. exit(1);
  392. }
  393. if (vacopts->min_mxid_age != 0 && PQserverVersion(conn) < 90600)
  394. {
  395. pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
  396. "--min-mxid-age", "9.6");
  397. exit(1);
  398. }
  399. if (vacopts->parallel_workers >= 0 && PQserverVersion(conn) < 130000)
  400. {
  401. pg_log_error("cannot use the \"%s\" option on server versions older than PostgreSQL %s",
  402. "--parallel", "13");
  403. exit(1);
  404. }
  405. if (!quiet)
  406. {
  407. if (stage != ANALYZE_NO_STAGE)
  408. printf(_("%s: processing database \"%s\": %s\n"),
  409. progname, PQdb(conn), _(stage_messages[stage]));
  410. else
  411. printf(_("%s: vacuuming database \"%s\"\n"),
  412. progname, PQdb(conn));
  413. fflush(stdout);
  414. }
  415. /*
  416. * Prepare the list of tables to process by querying the catalogs.
  417. *
  418. * Since we execute the constructed query with the default search_path
  419. * (which could be unsafe), everything in this query MUST be fully
  420. * qualified.
  421. *
  422. * First, build a WITH clause for the catalog query if any tables were
  423. * specified, with a set of values made of relation names and their
  424. * optional set of columns. This is used to match any provided column
  425. * lists with the generated qualified identifiers and to filter for the
  426. * tables provided via --table. If a listed table does not exist, the
  427. * catalog query will fail.
  428. */
  429. initPQExpBuffer(&catalog_query);
  430. for (cell = tables ? tables->head : NULL; cell; cell = cell->next)
  431. {
  432. char *just_table;
  433. const char *just_columns;
  434. /*
  435. * Split relation and column names given by the user, this is used to
  436. * feed the CTE with values on which are performed pre-run validity
  437. * checks as well. For now these happen only on the relation name.
  438. */
  439. splitTableColumnsSpec(cell->val, PQclientEncoding(conn),
  440. &just_table, &just_columns);
  441. if (!tables_listed)
  442. {
  443. appendPQExpBufferStr(&catalog_query,
  444. "WITH listed_tables (table_oid, column_list) "
  445. "AS (\n VALUES (");
  446. tables_listed = true;
  447. }
  448. else
  449. appendPQExpBufferStr(&catalog_query, ",\n (");
  450. appendStringLiteralConn(&catalog_query, just_table, conn);
  451. appendPQExpBufferStr(&catalog_query, "::pg_catalog.regclass, ");
  452. if (just_columns && just_columns[0] != '\0')
  453. appendStringLiteralConn(&catalog_query, just_columns, conn);
  454. else
  455. appendPQExpBufferStr(&catalog_query, "NULL");
  456. appendPQExpBufferStr(&catalog_query, "::pg_catalog.text)");
  457. pg_free(just_table);
  458. }
  459. /* Finish formatting the CTE */
  460. if (tables_listed)
  461. appendPQExpBufferStr(&catalog_query, "\n)\n");
  462. appendPQExpBufferStr(&catalog_query, "SELECT c.relname, ns.nspname");
  463. if (tables_listed)
  464. appendPQExpBufferStr(&catalog_query, ", listed_tables.column_list");
  465. appendPQExpBufferStr(&catalog_query,
  466. " FROM pg_catalog.pg_class c\n"
  467. " JOIN pg_catalog.pg_namespace ns"
  468. " ON c.relnamespace OPERATOR(pg_catalog.=) ns.oid\n"
  469. " LEFT JOIN pg_catalog.pg_class t"
  470. " ON c.reltoastrelid OPERATOR(pg_catalog.=) t.oid\n");
  471. /* Used to match the tables listed by the user */
  472. if (tables_listed)
  473. appendPQExpBufferStr(&catalog_query, " JOIN listed_tables"
  474. " ON listed_tables.table_oid OPERATOR(pg_catalog.=) c.oid\n");
  475. /*
  476. * If no tables were listed, filter for the relevant relation types. If
  477. * tables were given via --table, don't bother filtering by relation type.
  478. * Instead, let the server decide whether a given relation can be
  479. * processed in which case the user will know about it.
  480. */
  481. if (!tables_listed)
  482. {
  483. appendPQExpBufferStr(&catalog_query, " WHERE c.relkind OPERATOR(pg_catalog.=) ANY (array["
  484. CppAsString2(RELKIND_RELATION) ", "
  485. CppAsString2(RELKIND_MATVIEW) "])\n");
  486. has_where = true;
  487. }
  488. /*
  489. * For --min-xid-age and --min-mxid-age, the age of the relation is the
  490. * greatest of the ages of the main relation and its associated TOAST
  491. * table. The commands generated by vacuumdb will also process the TOAST
  492. * table for the relation if necessary, so it does not need to be
  493. * considered separately.
  494. */
  495. if (vacopts->min_xid_age != 0)
  496. {
  497. appendPQExpBuffer(&catalog_query,
  498. " %s GREATEST(pg_catalog.age(c.relfrozenxid),"
  499. " pg_catalog.age(t.relfrozenxid)) "
  500. " OPERATOR(pg_catalog.>=) '%d'::pg_catalog.int4\n"
  501. " AND c.relfrozenxid OPERATOR(pg_catalog.!=)"
  502. " '0'::pg_catalog.xid\n",
  503. has_where ? "AND" : "WHERE", vacopts->min_xid_age);
  504. has_where = true;
  505. }
  506. if (vacopts->min_mxid_age != 0)
  507. {
  508. appendPQExpBuffer(&catalog_query,
  509. " %s GREATEST(pg_catalog.mxid_age(c.relminmxid),"
  510. " pg_catalog.mxid_age(t.relminmxid)) OPERATOR(pg_catalog.>=)"
  511. " '%d'::pg_catalog.int4\n"
  512. " AND c.relminmxid OPERATOR(pg_catalog.!=)"
  513. " '0'::pg_catalog.xid\n",
  514. has_where ? "AND" : "WHERE", vacopts->min_mxid_age);
  515. has_where = true;
  516. }
  517. /*
  518. * Execute the catalog query. We use the default search_path for this
  519. * query for consistency with table lookups done elsewhere by the user.
  520. */
  521. appendPQExpBufferStr(&catalog_query, " ORDER BY c.relpages DESC;");
  522. executeCommand(conn, "RESET search_path;", echo);
  523. res = executeQuery(conn, catalog_query.data, echo);
  524. termPQExpBuffer(&catalog_query);
  525. PQclear(executeQuery(conn, ALWAYS_SECURE_SEARCH_PATH_SQL, echo));
  526. /*
  527. * If no rows are returned, there are no matching tables, so we are done.
  528. */
  529. ntups = PQntuples(res);
  530. if (ntups == 0)
  531. {
  532. PQclear(res);
  533. PQfinish(conn);
  534. return;
  535. }
  536. /*
  537. * Build qualified identifiers for each table, including the column list
  538. * if given.
  539. */
  540. initPQExpBuffer(&buf);
  541. for (i = 0; i < ntups; i++)
  542. {
  543. appendPQExpBufferStr(&buf,
  544. fmtQualifiedId(PQgetvalue(res, i, 1),
  545. PQgetvalue(res, i, 0)));
  546. if (tables_listed && !PQgetisnull(res, i, 2))
  547. appendPQExpBufferStr(&buf, PQgetvalue(res, i, 2));
  548. simple_string_list_append(&dbtables, buf.data);
  549. resetPQExpBuffer(&buf);
  550. }
  551. termPQExpBuffer(&buf);
  552. PQclear(res);
  553. /*
  554. * If there are more connections than vacuumable relations, we don't need
  555. * to use them all.
  556. */
  557. if (parallel)
  558. {
  559. if (concurrentCons > ntups)
  560. concurrentCons = ntups;
  561. if (concurrentCons <= 1)
  562. parallel = false;
  563. }
  564. /*
  565. * Setup the database connections. We reuse the connection we already have
  566. * for the first slot. If not in parallel mode, the first slot in the
  567. * array contains the connection.
  568. */
  569. if (concurrentCons <= 0)
  570. concurrentCons = 1;
  571. slots = ParallelSlotsSetup(dbname, host, port, username, prompt_password,
  572. progname, echo, conn, concurrentCons);
  573. /*
  574. * Prepare all the connections to run the appropriate analyze stage, if
  575. * caller requested that mode.
  576. */
  577. if (stage != ANALYZE_NO_STAGE)
  578. {
  579. int j;
  580. /* We already emitted the message above */
  581. for (j = 0; j < concurrentCons; j++)
  582. executeCommand((slots + j)->connection,
  583. stage_commands[stage], echo);
  584. }
  585. initPQExpBuffer(&sql);
  586. cell = dbtables.head;
  587. do
  588. {
  589. const char *tabname = cell->val;
  590. ParallelSlot *free_slot;
  591. if (CancelRequested)
  592. {
  593. failed = true;
  594. goto finish;
  595. }
  596. free_slot = ParallelSlotsGetIdle(slots, concurrentCons);
  597. if (!free_slot)
  598. {
  599. failed = true;
  600. goto finish;
  601. }
  602. prepare_vacuum_command(&sql, PQserverVersion(free_slot->connection),
  603. vacopts, tabname);
  604. /*
  605. * Execute the vacuum. All errors are handled in processQueryResult
  606. * through ParallelSlotsGetIdle.
  607. */
  608. run_vacuum_command(free_slot->connection, sql.data,
  609. echo, tabname);
  610. cell = cell->next;
  611. } while (cell != NULL);
  612. if (!ParallelSlotsWaitCompletion(slots, concurrentCons))
  613. failed = true;
  614. finish:
  615. ParallelSlotsTerminate(slots, concurrentCons);
  616. pg_free(slots);
  617. termPQExpBuffer(&sql);
  618. if (failed)
  619. exit(1);
  620. }
  621. /*
  622. * Vacuum/analyze all connectable databases.
  623. *
  624. * In analyze-in-stages mode, we process all databases in one stage before
  625. * moving on to the next stage. That ensure minimal stats are available
  626. * quickly everywhere before generating more detailed ones.
  627. */
  628. static void
  629. vacuum_all_databases(vacuumingOptions *vacopts,
  630. bool analyze_in_stages,
  631. const char *maintenance_db, const char *host,
  632. const char *port, const char *username,
  633. enum trivalue prompt_password,
  634. int concurrentCons,
  635. const char *progname, bool echo, bool quiet)
  636. {
  637. PGconn *conn;
  638. PGresult *result;
  639. PQExpBufferData connstr;
  640. int stage;
  641. int i;
  642. conn = connectMaintenanceDatabase(maintenance_db, host, port, username,
  643. prompt_password, progname, echo);
  644. result = executeQuery(conn,
  645. "SELECT datname FROM pg_database WHERE datallowconn ORDER BY 1;",
  646. echo);
  647. PQfinish(conn);
  648. initPQExpBuffer(&connstr);
  649. if (analyze_in_stages)
  650. {
  651. /*
  652. * When analyzing all databases in stages, we analyze them all in the
  653. * fastest stage first, so that initial statistics become available
  654. * for all of them as soon as possible.
  655. *
  656. * This means we establish several times as many connections, but
  657. * that's a secondary consideration.
  658. */
  659. for (stage = 0; stage < ANALYZE_NUM_STAGES; stage++)
  660. {
  661. for (i = 0; i < PQntuples(result); i++)
  662. {
  663. resetPQExpBuffer(&connstr);
  664. appendPQExpBufferStr(&connstr, "dbname=");
  665. appendConnStrVal(&connstr, PQgetvalue(result, i, 0));
  666. vacuum_one_database(connstr.data, vacopts,
  667. stage,
  668. NULL,
  669. host, port, username, prompt_password,
  670. concurrentCons,
  671. progname, echo, quiet);
  672. }
  673. }
  674. }
  675. else
  676. {
  677. for (i = 0; i < PQntuples(result); i++)
  678. {
  679. resetPQExpBuffer(&connstr);
  680. appendPQExpBufferStr(&connstr, "dbname=");
  681. appendConnStrVal(&connstr, PQgetvalue(result, i, 0));
  682. vacuum_one_database(connstr.data, vacopts,
  683. ANALYZE_NO_STAGE,
  684. NULL,
  685. host, port, username, prompt_password,
  686. concurrentCons,
  687. progname, echo, quiet);
  688. }
  689. }
  690. termPQExpBuffer(&connstr);
  691. PQclear(result);
  692. }
  693. /*
  694. * Construct a vacuum/analyze command to run based on the given options, in the
  695. * given string buffer, which may contain previous garbage.
  696. *
  697. * The table name used must be already properly quoted. The command generated
  698. * depends on the server version involved and it is semicolon-terminated.
  699. */
  700. static void
  701. prepare_vacuum_command(PQExpBuffer sql, int serverVersion,
  702. vacuumingOptions *vacopts, const char *table)
  703. {
  704. const char *paren = " (";
  705. const char *comma = ", ";
  706. const char *sep = paren;
  707. resetPQExpBuffer(sql);
  708. if (vacopts->analyze_only)
  709. {
  710. appendPQExpBufferStr(sql, "ANALYZE");
  711. /* parenthesized grammar of ANALYZE is supported since v11 */
  712. if (serverVersion >= 110000)
  713. {
  714. if (vacopts->skip_locked)
  715. {
  716. /* SKIP_LOCKED is supported since v12 */
  717. Assert(serverVersion >= 120000);
  718. appendPQExpBuffer(sql, "%sSKIP_LOCKED", sep);
  719. sep = comma;
  720. }
  721. if (vacopts->verbose)
  722. {
  723. appendPQExpBuffer(sql, "%sVERBOSE", sep);
  724. sep = comma;
  725. }
  726. if (sep != paren)
  727. appendPQExpBufferChar(sql, ')');
  728. }
  729. else
  730. {
  731. if (vacopts->verbose)
  732. appendPQExpBufferStr(sql, " VERBOSE");
  733. }
  734. }
  735. else
  736. {
  737. appendPQExpBufferStr(sql, "VACUUM");
  738. /* parenthesized grammar of VACUUM is supported since v9.0 */
  739. if (serverVersion >= 90000)
  740. {
  741. if (vacopts->disable_page_skipping)
  742. {
  743. /* DISABLE_PAGE_SKIPPING is supported since v9.6 */
  744. Assert(serverVersion >= 90600);
  745. appendPQExpBuffer(sql, "%sDISABLE_PAGE_SKIPPING", sep);
  746. sep = comma;
  747. }
  748. if (vacopts->skip_locked)
  749. {
  750. /* SKIP_LOCKED is supported since v12 */
  751. Assert(serverVersion >= 120000);
  752. appendPQExpBuffer(sql, "%sSKIP_LOCKED", sep);
  753. sep = comma;
  754. }
  755. if (vacopts->full)
  756. {
  757. appendPQExpBuffer(sql, "%sFULL", sep);
  758. sep = comma;
  759. }
  760. if (vacopts->freeze)
  761. {
  762. appendPQExpBuffer(sql, "%sFREEZE", sep);
  763. sep = comma;
  764. }
  765. if (vacopts->verbose)
  766. {
  767. appendPQExpBuffer(sql, "%sVERBOSE", sep);
  768. sep = comma;
  769. }
  770. if (vacopts->and_analyze)
  771. {
  772. appendPQExpBuffer(sql, "%sANALYZE", sep);
  773. sep = comma;
  774. }
  775. if (vacopts->parallel_workers >= 0)
  776. {
  777. /* PARALLEL is supported since v13 */
  778. Assert(serverVersion >= 130000);
  779. appendPQExpBuffer(sql, "%sPARALLEL %d", sep,
  780. vacopts->parallel_workers);
  781. sep = comma;
  782. }
  783. if (sep != paren)
  784. appendPQExpBufferChar(sql, ')');
  785. }
  786. else
  787. {
  788. if (vacopts->full)
  789. appendPQExpBufferStr(sql, " FULL");
  790. if (vacopts->freeze)
  791. appendPQExpBufferStr(sql, " FREEZE");
  792. if (vacopts->verbose)
  793. appendPQExpBufferStr(sql, " VERBOSE");
  794. if (vacopts->and_analyze)
  795. appendPQExpBufferStr(sql, " ANALYZE");
  796. }
  797. }
  798. appendPQExpBuffer(sql, " %s;", table);
  799. }
  800. /*
  801. * Send a vacuum/analyze command to the server, returning after sending the
  802. * command.
  803. *
  804. * Any errors during command execution are reported to stderr.
  805. */
  806. static void
  807. run_vacuum_command(PGconn *conn, const char *sql, bool echo,
  808. const char *table)
  809. {
  810. bool status;
  811. if (echo)
  812. printf("%s\n", sql);
  813. status = PQsendQuery(conn, sql) == 1;
  814. if (!status)
  815. {
  816. if (table)
  817. pg_log_error("vacuuming of table \"%s\" in database \"%s\" failed: %s",
  818. table, PQdb(conn), PQerrorMessage(conn));
  819. else
  820. pg_log_error("vacuuming of database \"%s\" failed: %s",
  821. PQdb(conn), PQerrorMessage(conn));
  822. }
  823. }
  824. static void
  825. help(const char *progname)
  826. {
  827. printf(_("%s cleans and analyzes a PostgreSQL database.\n\n"), progname);
  828. printf(_("Usage:\n"));
  829. printf(_(" %s [OPTION]... [DBNAME]\n"), progname);
  830. printf(_("\nOptions:\n"));
  831. printf(_(" -a, --all vacuum all databases\n"));
  832. printf(_(" -d, --dbname=DBNAME database to vacuum\n"));
  833. printf(_(" --disable-page-skipping disable all page-skipping behavior\n"));
  834. printf(_(" -e, --echo show the commands being sent to the server\n"));
  835. printf(_(" -f, --full do full vacuuming\n"));
  836. printf(_(" -F, --freeze freeze row transaction information\n"));
  837. printf(_(" -j, --jobs=NUM use this many concurrent connections to vacuum\n"));
  838. printf(_(" --min-mxid-age=MXID_AGE minimum multixact ID age of tables to vacuum\n"));
  839. printf(_(" --min-xid-age=XID_AGE minimum transaction ID age of tables to vacuum\n"));
  840. printf(_(" -P, --parallel=PARALLEL_DEGREE use this many background workers for vacuum, if available\n"));
  841. printf(_(" -q, --quiet don't write any messages\n"));
  842. printf(_(" --skip-locked skip relations that cannot be immediately locked\n"));
  843. printf(_(" -t, --table='TABLE[(COLUMNS)]' vacuum specific table(s) only\n"));
  844. printf(_(" -v, --verbose write a lot of output\n"));
  845. printf(_(" -V, --version output version information, then exit\n"));
  846. printf(_(" -z, --analyze update optimizer statistics\n"));
  847. printf(_(" -Z, --analyze-only only update optimizer statistics; no vacuum\n"));
  848. printf(_(" --analyze-in-stages only update optimizer statistics, in multiple\n"
  849. " stages for faster results; no vacuum\n"));
  850. printf(_(" -?, --help show this help, then exit\n"));
  851. printf(_("\nConnection options:\n"));
  852. printf(_(" -h, --host=HOSTNAME database server host or socket directory\n"));
  853. printf(_(" -p, --port=PORT database server port\n"));
  854. printf(_(" -U, --username=USERNAME user name to connect as\n"));
  855. printf(_(" -w, --no-password never prompt for password\n"));
  856. printf(_(" -W, --password force password prompt\n"));
  857. printf(_(" --maintenance-db=DBNAME alternate maintenance database\n"));
  858. printf(_("\nRead the description of the SQL command VACUUM for details.\n"));
  859. printf(_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
  860. printf(_("%s home page: <%s>\n"), PACKAGE_NAME, PACKAGE_URL);
  861. }