sql - 为什么这个索引会使 SELECT GROUP BY 查询变慢?

标签 sql sqlite indexing group-by

假设我们有一个包含 10 万笔交易的表格(每行是 product 在时间戳 customer 购买的 dt)。

我注意到查询

SELECT product, COUNT(customer) FROM transactions GROUP BY product

没有索引时出奇地快:

<表类="s-表"> <头> <日> 数据库大小 SELECT查询时间 <正文> 没有索引 1.8MB 63 毫秒 索引 product 3MB 220 毫秒 索引 product + 索引 customer 4.1MB 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 optimizeANALYZE在填充的表上生成统计数据,为它提供更好的信息来进行猜测。它还可以决定不使用任何现有索引,并可能使用 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 的计数,导致大量的磁盘寻道。没有任何索引,它将顺序读取表,使用临时数据结构来构建结果。这最终变得更快(磁盘读取速度很慢)。

您可以在 query planning 中阅读更多关于 sqlite 如何使用索引的信息文档。

正如您所发现的,这里最好的方法是使用多列覆盖索引,将所有需要的信息存储在索引本身中,这样就永远不必查询表本身:

CREATE INDEX product_customer_idx ON transactions(product, customer);

关于sql - 为什么这个索引会使 SELECT GROUP BY 查询变慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65470977/

相关文章:

java - 声明关闭问题后不允许进行任何操作

c# - SQL CE 3.5 部署问题,涉及 C# 和 C++ 之间的互操作

java - 在这种情况下如何使用jOOQ(或其他东西)

sql - 为什么 SQLITE 在一个简单的计数查询上要花很长时间?

python - 是否可以在 Pandas 中将 searchsorted 与 MultiIndex 索引一起使用?

indexing - postgresql 索引使用 - 优点和缺点

android - SQLite 中的重复条目

sql - SQLite 中的 CREATE TABLE IF NOT EXISTS 语句

javascript - 如何同步Nodejs函数

arrays - 根据值从数组中选择索引