diff --git a/docs.json b/docs.json index 7d896d6..c119294 100644 --- a/docs.json +++ b/docs.json @@ -319,6 +319,7 @@ "features/join-builder", "features/explain-visualization", "features/server-dashboard", + "features/table-health", "features/users-roles" ] }, diff --git a/features/overview.mdx b/features/overview.mdx index 85859f6..e95199d 100644 --- a/features/overview.mdx +++ b/features/overview.mdx @@ -135,6 +135,9 @@ First time here: [Quick Start](/quickstart) gets you from install to a query wit Live server stats and activity. + + Bloat, unused indexes, missing keys. + Users, roles, and privileges on MySQL and PostgreSQL. diff --git a/features/table-health.mdx b/features/table-health.mdx new file mode 100644 index 0000000..b8b5698 --- /dev/null +++ b/features/table-health.mdx @@ -0,0 +1,66 @@ +--- +title: Table Health +description: Find bloated tables, unused and redundant indexes, tables without a primary key, and foreign keys without an index +--- + +Open it from **Database > Table Health**. The report reads the catalogs and statistics of the database selected in the sidebar and changes nothing. Press `Cmd+R` (**Database > Refresh**) or click the refresh button to read them again. + + + Table Health report with an unused index selected + Table Health report with an unused index selected + + +## What each engine checks + +| Check | PostgreSQL | MySQL and MariaDB | +|---|---|---| +| Bloated tables | At least 1,000 dead rows and at least a fifth of the live rows, from `pg_stat_user_tables`, with the last vacuum | Not checked | +| Unused indexes | Never scanned, from `pg_stat_user_indexes` | No reads recorded, from `performance_schema` | +| Redundant indexes | Not checked | An index whose columns lead another index of the same type. Full-text indexes are not compared, since two can use different parsers | +| Tables without a primary key | Yes | Yes | +| Foreign keys without an index | No index starts with the key's columns | Not checked: InnoDB indexes a foreign key's columns itself | + +The report covers the database selected in the sidebar, in every schema on PostgreSQL. The toolbar names it. The menu item is dimmed for other engines. + +A unique index, a primary key and an index behind a constraint never count as unused: they enforce something even when no query reads them. Neither does an index that starts with a foreign key's columns: the key's checks need it, and on MySQL and MariaDB they read it without counting a read. On MySQL and MariaDB a full-text index is not listed as unused either, since its searches are not counted. A partitioned table is listed once, not once per partition. + +## Read the statistics window + +An index looks unused when nothing scanned it since the counters started. The note at the top of the list says when that was: + +- **PostgreSQL**: when the database's statistics were last reset, or that they never were. +- **MySQL and MariaDB**: when the server started, since `performance_schema` counts from there. An index on a table with no recorded reads or writes since then is not listed. + +When the server records nothing to read, the check says why instead of listing every index: + +- **PostgreSQL**: `track_counts` is off. The bloat check is skipped too. +- **MySQL and MariaDB**: `performance_schema` is off, the `wait/io/table/sql/handler` instrument or the `global_instrumentation` consumer is disabled, or no table in the database has a recorded read since the server started. MariaDB ships with `performance_schema` off; set `performance_schema=ON` and restart to turn it on. + +Check an index against a period of normal traffic before dropping it. One a monthly report reads looks unused for the other 29 days. + +## Act on a finding + +Select a finding to preview the SQL that fixes it: + +| Finding | Preview | +|---|---| +| Bloated table | `VACUUM (ANALYZE)` | +| Unused or redundant index | `DROP INDEX CONCURRENTLY` on PostgreSQL, `ALTER TABLE orders DROP INDEX orders_note_idx` on MySQL | +| Table without a primary key | `ALTER TABLE logs ADD PRIMARY KEY (column_name)`. Replace `column_name` with the columns that identify a row | +| Foreign key without an index | `CREATE INDEX CONCURRENTLY` on the key's columns, or `CREATE INDEX` on a partitioned table, which cannot build one concurrently | + +TablePro never runs the preview. **Open in Editor** puts it in a new query tab for you to review and run. **Copy** puts it on the clipboard. **Open Table**, or a double-click on the finding, opens the table. The same actions are in the finding's context menu. + +`VACUUM`, `CREATE INDEX CONCURRENTLY` and `DROP INDEX CONCURRENTLY` cannot run inside a transaction, so run them with auto-commit on. + +## When a check fails + +A check the account cannot read shows the server's error in its own section, and the other checks still run. On MySQL and MariaDB, the unused index check needs `SELECT` on `performance_schema`. A connection with no database selected asks for one. diff --git a/images/table-health-dark.png b/images/table-health-dark.png new file mode 100644 index 0000000..4857af7 Binary files /dev/null and b/images/table-health-dark.png differ diff --git a/images/table-health.png b/images/table-health.png new file mode 100644 index 0000000..350aeac Binary files /dev/null and b/images/table-health.png differ