sqlite-utils analyze command, refs #379

This commit is contained in:
Simon Willison 2022-01-10 17:24:17 -08:00
commit e0ef9288fe
4 changed files with 76 additions and 1 deletions

View file

@ -1800,6 +1800,23 @@ If the ``_counts`` table ever becomes out-of-sync with the actual table counts y
$ sqlite-utils reset-counts mydb.db
.. _cli_analyze:
Optimizing index usage with ANALYZE
===================================
The `SQLite ANALYZE command <https://www.sqlite.org/lang_analyze.html>`__ builds a table of statistics which the query planner can use to make better decisions about which indexes to use for a given query.
You should run ``ANALYZE`` if your database is large and you do not think your indexes are being efficiently used.
To run ``ANALYZE`` against every index in a database, use this::
$ sqlite-utils analyze mydb.db
You can run it against specific tables, or against specific named indexes, by passing them as optional arguments::
$ sqlite-utils analyze mydb.db mytable idx_mytable_name
.. _cli_vacuum:
Vacuum

View file

@ -2214,7 +2214,7 @@ Pass ``analyze=True`` to run ``ANALYZE`` against the new index after creating it
Optimizing index usage with ANALYZE
===================================
The `SQLite ANALYZE command <https://www.sqlite.org/lang_analyze.html>`__ can be used to build a table of statistics which the query planner can then use to make better decisions about which indexes to use for a given query.
The `SQLite ANALYZE command <https://www.sqlite.org/lang_analyze.html>`__ builds a table of statistics which the query planner can use to make better decisions about which indexes to use for a given query.
You should run ``ANALYZE`` if your database is large and you do not think your indexes are being efficiently used.

View file

@ -304,6 +304,26 @@ def rebuild_fts(path, tables, load_extension):
db[table].rebuild_fts()
@cli.command()
@click.argument(
"path",
type=click.Path(exists=True, file_okay=True, dir_okay=False, allow_dash=False),
required=True,
)
@click.argument("names", nargs=-1)
def analyze(path, names):
"""Run ANALYZE against the whole database, or against specific named indexes and tables"""
db = sqlite_utils.Database(path)
try:
if names:
for name in names:
db.analyze(name)
else:
db.analyze()
except sqlite3.OperationalError as e:
raise click.ClickException(e)
@cli.command()
@click.argument(
"path",

View file

@ -2057,3 +2057,41 @@ def test_create_database(tmpdir, enable_wal):
assert db.journal_mode == "wal"
else:
assert db.journal_mode == "delete"
@pytest.mark.parametrize(
"options,expected",
(
(
[],
[
{"tbl": "two_indexes", "idx": "idx_two_indexes_species", "stat": "1 1"},
{"tbl": "two_indexes", "idx": "idx_two_indexes_name", "stat": "1 1"},
{"tbl": "one_index", "idx": "idx_one_index_name", "stat": "1 1"},
],
),
(
["one_index"],
[
{"tbl": "one_index", "idx": "idx_one_index_name", "stat": "1 1"},
],
),
(
["idx_two_indexes_name"],
[
{"tbl": "two_indexes", "idx": "idx_two_indexes_name", "stat": "1 1"},
],
),
),
)
def test_analyze(tmpdir, options, expected):
db_path = str(tmpdir / "test.db")
db = Database(db_path)
db["one_index"].insert({"id": 1, "name": "Cleo"}, pk="id")
db["one_index"].create_index(["name"])
db["two_indexes"].insert({"id": 1, "name": "Cleo", "species": "dog"}, pk="id")
db["two_indexes"].create_index(["name"])
db["two_indexes"].create_index(["species"])
result = CliRunner().invoke(cli.cli, ["analyze", db_path] + options)
assert result.exit_code == 0
assert list(db["sqlite_stat1"].rows) == expected