【问题标题】:Why has changing my table schema slowed down my queries?为什么更改我的表模式会减慢我的查询速度?
【发布时间】:2014-07-02 16:33:05
【问题描述】:

今天我对表进行了一些更改,试图使某些类型的查询运行得更快。这是表格(在我更改之前):

CREATE TABLE IF NOT EXISTS street_addresses (
  id INTEGER PRIMARY KEY NOT NULL,
  house_number INTEGER NOT NULL,
  entrance TEXT NOT NULL,
  latitude REAL NOT NULL,
  longitude REAL NOT NULL,
  street_name INTEGER NOT NULL REFERENCES street_names(id),
  postal_code INTEGER NOT NULL REFERENCES postal_codes(id),
  city INTEGER NOT NULL REFERENCES cities(id),
  municipality INTEGER NOT NULL REFERENCES municipalities(id),
  CONSTRAINT unique_address UNIQUE(
    street_name, house_number, entrance, postal_code, city
  )
)

这个表有两个索引(我可以识别):主键和跨 5 列的唯一键。我经常需要仅使用 门牌号邮政编码 列或 门牌号城市 列,所以我将创建表的 SQL 更改为:

CREATE TABLE IF NOT EXISTS street_addresses (
  id INTEGER PRIMARY KEY NOT NULL,
  house_number INTEGER NOT NULL,
  entrance TEXT NOT NULL,
  latitude REAL NOT NULL,
  longitude REAL NOT NULL,
  street_name INTEGER NOT NULL REFERENCES street_names,
  postal_code INTEGER NOT NULL REFERENCES postal_codes,
  city INTEGER NOT NULL REFERENCES cities,
  municipality INTEGER NOT NULL REFERENCES municipalities
);
CREATE INDEX IF NOT EXISTS sa_hn_pc
  ON street_addresses (house_number, postal_code);
CREATE INDEX IF NOT EXISTS sa_hn_ci
  ON street_addresses (house_number, city);
CREATE UNIQUE INDEX IF NOT EXISTS sa_unique_address
  ON street_addresses (
    street_name, house_number, entrance, postal_code, city
  );

我添加了两个索引并将 UNIQUE 索引移出表定义(以便我将所有键放在一个位置。)此外,我从 REFERENCES 行中删除了 (id),因为根据文档无论如何,它默认使用主键。我的数据库现在明显变大了,但至少使用门牌号和邮政编码获取地址要快几十倍!

不幸的是,按街道名称和门牌号搜索的查询(这是我的数据库最常用的查询类型)似乎不再使用我的索引。在我得到表更改之前使用街道名称和门牌号每秒读取约 1700 次,现在我得到约 50 次。如果我使用所有 5 列进行搜索,我仍然可以获得良好的旧速度,但现在只使用 UNIQUE 键中的前 2 列非常慢。

此外,使用门牌号和城市的查询仍然几乎和以前一样慢,比使用门牌号和邮政编码的搜索要慢得多。

知道这是怎么发生的吗?我是否需要为街道名称和门牌号定义一个新索引,即使这些列是 UNIQUE 键的一部分?如果是这样,为什么我之前的查询这么快?另外,为什么门牌号和城市查询没有像门牌号和邮政编码查询一样获得同样的速度提升?

对不起,文字墙。我希望有人能帮忙。这是我正在使用的选择查询:


我的基准测试:

换桌前:

$ bin/benchmark_norway_database --search-by-components 10000 --street_name --house_number
[ ============================= 100% (10000/10000) ============== =============== ]
5.9129 秒
每个间隔 0.0006 秒
每秒 1691 个间隔

$ bin/benchmark_norway_database --search-by-components 10000 --street_name --house_number --entrance --postal_code --city
[ ============================= 100% (10000/10000) ============== =============== ]
3.2198 秒
每个间隔 0.0003 秒
每秒 3106 个间隔

$ bin/benchmark_norway_database --search-by-components 100 --house_number --postal_code
[ =============================== 100% (100/100) ============ ===================]
9.957 秒
每间隔 0.0996 秒
每秒 10 个间隔

$ bin/benchmark_norway_database --search-by-components 100 --house_number --city
[ =============================== 100% (100/100) ============ ===================]
10.2446 秒
每个间隔 0.1024 秒
每秒 10 个间隔

换表后:

# 现在速度太慢了,我不能做 10000 个间隔。
$ bin/benchmark_norway_database --search-by-components 500 --street_name --house_number
[ ============================== 100% (500/500) ============ ===================]
9.5749 秒
每个间隔 0.0191 秒
每秒 52 个间隔

# 还是很快!
$ bin/benchmark_norway_database --search-by-components 10000 --street_name --house_number --entrance --postal_code --city
[ ============================= 100% (10000/10000) ============== =============== ]
3.4125 秒
每个间隔 0.0003 秒
每秒 2930 个间隔

# 比以前快得多!
$ bin/benchmark_norway_database --search-by-components 10000 --house_number --postal_code
[ ============================= 100% (10000/10000) ============== =============== ]
22.2646 秒
每个间隔 0.0022 秒
每秒 449 个间隔

# 还慢吗?为什么? :S
$ bin/benchmark_norway_database --search-by-components 500 --house_number --city
[ ============================== 100% (500/500) ============ ===================]
14.3483 秒
每个间隔 0.0287 秒
每秒 35 个间隔

我的选择查询:

SELECT
  sn.name, sa.house_number, sa.entrance, pc.postal_code,
  ci.name, mu.name, co.name, sa.latitude, sa.longitude
FROM
  street_addresses AS sa
  INNER JOIN street_names   AS sn ON sa.street_name  = sn.id
  INNER JOIN postal_codes   AS pc ON sa.postal_code  = pc.id
  INNER JOIN cities         AS ci ON sa.city         = ci.id
  INNER JOIN municipalities AS mu ON sa.municipality = mu.id
  INNER JOIN counties       AS co ON mu.county       = co.id
WHERE
  ...
ORDER BY
  ci.name ASC, sn.name ASC, sa.house_number ASC, sa.entrance ASC
LIMIT
  0, 100

注意:在WHERE 部分,我在搜索街道名称时使用 GLOB,例如:

WHERE
  sn.name GLOB "FORNEBUVEIEN" AND
  sa.house_number = 11

我所有的表模式,假设它们是相关的:

CREATE TABLE IF NOT EXISTS counties (
  id INTEGER PRIMARY KEY NOT NULL,
  name TEXT UNIQUE NOT NULL
)

CREATE TABLE IF NOT EXISTS municipalities (
  id INTEGER PRIMARY KEY NOT NULL,
  name TEXT NOT NULL,
  number INTEGER NOT NULL,
  county INTEGER NOT NULL REFERENCES counties,
  CONSTRAINT unique_municipality UNIQUE(name, county)
);
CREATE UNIQUE INDEX IF NOT EXISTS mu_number
  ON municipalities (number);
CREATE UNIQUE INDEX IF NOT EXISTS mu_unique_name_co
  ON municipalities (name, county);

CREATE TABLE IF NOT EXISTS cities (
  id INTEGER PRIMARY KEY NOT NULL,
  name TEXT NOT NULL,
  municipality INTEGER NOT NULL REFERENCES municipalities
);
CREATE UNIQUE INDEX IF NOT EXISTS ci_unique_name_mu
  ON cities (name, municipality);

CREATE TABLE IF NOT EXISTS postal_codes (
  id INTEGER PRIMARY KEY NOT NULL,
  postal_code INTEGER NOT NULL,
  city INTEGER NOT NULL REFERENCES cities
);
CREATE UNIQUE INDEX IF NOT EXISTS po_postal_code
  ON postal_codes (postal_code);

CREATE TABLE IF NOT EXISTS street_names (
  id INTEGER PRIMARY KEY NOT NULL,
  name TEXT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS sn_name
  ON street_names (name);

CREATE TABLE IF NOT EXISTS street_addresses (
  id INTEGER PRIMARY KEY NOT NULL,
  house_number INTEGER NOT NULL,
  entrance TEXT NOT NULL,
  latitude REAL NOT NULL,
  longitude REAL NOT NULL,
  street_name INTEGER NOT NULL REFERENCES street_names,
  postal_code INTEGER NOT NULL REFERENCES postal_codes,
  city INTEGER NOT NULL REFERENCES cities,
  municipality INTEGER NOT NULL REFERENCES municipalities
);
CREATE INDEX IF NOT EXISTS sa_hn_pc
  ON street_addresses (house_number, postal_code);
CREATE INDEX IF NOT EXISTS sa_hn_ci
  ON street_addresses (house_number, city);
CREATE UNIQUE INDEX IF NOT EXISTS sa_unique_address
  ON street_addresses (
    street_name, house_number, entrance, postal_code, city
  );

我在导入所有数据后运行这些命令:

PRAGMA journal_mode = OFF
PRAGMA page_size = 65536
VACUUM

使用街道名称和门牌号时解释查询计划:

sqlite> EXPLAIN QUERY PLAN SELECT sn.name, sa.house_number, sa.entrance, pc.postal_code, ci.name, mu.name, co.name, sa.latitude, sa.longitude FROM street_addresses AS sa INNER JOIN street_names AS sn ON sa.street_name = sn.id INNER JOIN postal_codes AS pc ON sa.postal_code = pc.id INNER JOIN city AS ci ON sa.city = ci.id INNER JOIN Municipalities AS mu ON sa.municipality = mu.id INNER JOIN 县 AS co ON mu.county = co.id WHERE sn.name GLOB "FORNEBUVEIEN" AND sa.house_number=11 ORDER BY ci.name ASC, sn.name ASC, sa.house_number ASC, sa.entrance ASC LIMIT 0 , 100;
从详细信息中选择订单
---------- ---------- ---------- -------- -------------------------------------------------- ---
0 0 0 搜索表 street_addresses AS sa USING INDEX sa_hn_ci (house_number=?)
0 1 1 搜索表 street_names AS sn 使用整数主键 (rowid=?)
0 2 2 SEARCH TABLE postal_codes AS pc USING INTEGER PRIMARY KEY (rowid=?)
0 3 3 搜索表城市 AS ci 使用整数主键 (rowid=?)
0 4 4 使用整数主键 (rowid=?) 搜索表城市作为 mu
0 5 5 搜索表县作为共同使用整数主键 (rowid=?)
0 0 0 使用 TEMP B-TREE FOR ORDER BY

【问题讨论】:

  • 针对慢速查询显示EXPLAIN QUERY PLAN 的输出。
  • @CL:谢谢。因此,当我使用GLOB 搜索街道名称和门牌号时,SQLite3 似乎只是不小心使用了错误的索引。它应该使用sa_unique_address,但它使用sa_hn_ci。当我使用 UNIQUE 键中的所有列时,即使我使用的是 GLOB,它也会使用正确的索引。我跑了ANALYZE,但它仍然选择了错误的索引。
  • 为什么是GLOB 而不是=
  • @CL.: 街道名称列必须支持通配符。

标签: sql database performance sqlite


【解决方案1】:

事实证明,当在我的 SELECT 查询中使用这样的 WHERE 部分时:

WHERE
  sn.name GLOB ? AND
  sa.house_number = ?

SQLite3 选择索引sa_hn_ci (house_number, city) 而不是sa_unique_address。这使得查询的运行速度慢了大约 100 倍。

我现在正在使用INDEXED BY 来解决这个问题,只要我的查询包含街道名称:

SELECT
  sn.name, sa.house_number, sa.entrance, pc.postal_code,
  ci.name, mu.name, co.name, sa.latitude, sa.longitude
FROM
  street_addresses AS sa INDEXED BY sa_unique_address          -- This line!
  INNER JOIN street_names   AS sn ON sa.street_name  = sn.id
  INNER JOIN postal_codes   AS pc ON sa.postal_code  = pc.id
  INNER JOIN cities         AS ci ON sa.city         = ci.id
  INNER JOIN municipalities AS mu ON sa.municipality = mu.id
  INNER JOIN counties       AS co ON mu.county       = co.id
WHERE
  sn.name GLOB "FORNEBUVEIEN" AND
  sa.house_number=11
ORDER BY
  ci.name ASC, sn.name ASC, sa.house_number ASC, sa.entrance ASC
LIMIT
  0, 100;

但我不知道为什么 SQLite3 一开始就选择了错误的索引。运行 ANALYZE 并没有改变任何东西。

我没有将此答案标记为正确。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-12-04
    • 2017-12-24
    • 1970-01-01
    • 2011-05-27
    • 1970-01-01
    相关资源
    最近更新 更多