假设我们有一个包含 10 万笔交易的表格(每行是 product
在时间戳 customer
购买的 dt
)。
我注意到查询
SELECT product, COUNT(customer) FROM transactions GROUP BY product
当没有索引时出奇地快:
product
product
+ 索引 customer
为什么在这种情况下索引会使查询变慢?(而且数据库更大,所以这是一个很大的否定!)
可重现的代码:
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
的计数,导致大量的磁盘寻道。没有任何索引,它将顺序读取表,使用临时数据结构来构建结果。这最终变得更快(磁盘读取速度很慢)。
您可以在 query planning 中阅读更多关于 sqlite 如何使用索引的信息文档。
正如您所发现的,这里最好的方法是使用多列覆盖索引,将所有需要的信息存储在索引本身中,这样就永远不必查询表本身:
CREATE INDEX product_customer_idx ON transactions(product, customer);
关于sql - 为什么这个索引会使 SELECT GROUP BY 查询变慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65470977/