sql - 为什么这个索引会使 SELECT GROUP BY 查询变慢?
问题描述
假设我们有一个包含 10 万笔交易的表(每一行都是product
按customer
时间戳购买的dt
)。
我注意到查询
SELECT product, COUNT(customer) FROM transactions GROUP BY product
当没有索引时,速度出奇地快:
数据库大小 | SELECT查询时间 | |
---|---|---|
无索引 | 1.8 MB | 63 毫秒 |
索引product |
3 MB | 220 毫秒 |
索引product + 索引customer |
4.1 MB | 292 毫秒 |
在这种情况下,为什么索引会使查询变慢?(而且数据库更大,所以这是一个很大的不!)
可重现的代码:
import sqlite3, time, random, string
db = sqlite3.connect('test.db')
db.executescript("""CREATE TABLE transactions(id INTEGER PRIMARY KEY, dt INTEGER, product TEXT, customer TEXT);
CREATE INDEX product_idx ON transactions(product);
CREATE INDEX customer_idx ON transactions(customer);""")
for i in range(100*1000):
t = random.randint(1600000000, 1600010000) # random datetime
product = ''.join(random.choices(string.ascii_uppercase, k=2)) # random product among 676 products
customer = ''.join(random.choices(string.ascii_uppercase, k=2)) # random customer among 676 customers
db.execute("INSERT INTO transactions(dt, product, customer) VALUES (?, ?, ?)", (t, product, customer))
t0 = time.time()
for _ in db.execute("SELECT product, COUNT(customer) FROM transactions GROUP BY product"):
pass
print (time.time()-t0)
db.commit()
解决方案
Sqlite 在查询中为每个表使用一个索引。在像您这样有多个可能索引的情况下,它会猜测使用哪个索引。您可以使用EXPLAIN QUERY PLAN
来查看选择了哪个,PRAGMA optimize
或者ANALYZE
在填充的表上生成统计信息,从而为它提供更好的信息来进行猜测。它还可以决定不使用任何现有索引,并且可能使用一个AUTOMATIC
索引,该索引是为查询构建的临时索引,然后在完成返回行时删除(这自然比使用现有索引需要更多时间,因此只会发生当 sqlite 认为它仍然会更快时)。
有一张空桌子:
sqlite> EXPLAIN QUERY PLAN SELECT product, COUNT(customer) FROM transactions GROUP BY product;
QUERY PLAN
`--SCAN TABLE transactions USING INDEX product_idx
sqlite> DROP INDEX product_idx;
sqlite> EXPLAIN QUERY PLAN SELECT product, COUNT(customer) FROM transactions GROUP BY product;
QUERY PLAN
|--SCAN TABLE transactions
`--USE TEMP B-TREE FOR GROUP BY
在这种情况下,由于您对product
列进行分组,因此使用该索引。但是它仍然必须读取每组的每一行才能得到 的计数customer
,从而导致大量的磁盘查找。没有任何索引,它将顺序读取表,使用临时数据结构来构建结果。这最终会更快(磁盘读取速度很慢)。
您可以在查询计划文档中阅读有关 sqlite 如何使用索引的更多信息。
正如您所发现的,这里最好的方法是使用多列覆盖索引,该索引将所有需要的信息存储在索引本身中,因此不必查询正确的表:
CREATE INDEX product_customer_idx ON transactions(product, customer);
推荐阅读
- python - 如何在使用 Selenium 和 Python 自动填充其默认文本之前填充 javascript 小部件
- c# - 空字符串数组
- django - 为 many=true 序列化操作序列化结果
- java - 为什么 MockBean 会抛出 NullPointer 异常
- json - 在 SharePoint Online 上显示格式正确的 JSON
- php - 连接同一数据库中另一个表中的一个完整表和一列,并使用 PHP 显示
- docker - 无法从外部到达 docker 容器
- python - 使用 Python 从随机 url 自动下载 csv 文件
- spring-boot - 获取 Spring RestTemplate 请求超时但未设置 connectionTimeout 和 ReadTimeout
- java - 当故障转移功能在 JAXWS SOAP 中处于活动状态时如何报告?