SQLite 是否优化了 WHERE 子句中具有多个 AND 条件的查询?
Does SQLite optimize a query with multiple AND conditions in the WHERE clause?
在 SQL 数据库中(我使用 Python+Sqlite),如果我们有 100 万行,如何确保查询
SELECT * FROM mytable WHERE myfunction(description) < 500 AND column2 < 1000
[-----------------------------] [--------------]
high-CPU cost condition easy-to-test
requiring 100 µs per test condition
已优化,以便第一个条件(CPU-昂贵) 仅在易于测试的第二个条件已经为真时才测试? (既然是合乎逻辑的AND
,是不是懒惰的AND
?)
示例:
如果始终测试第一个条件,则需要 100 万 x 100 微秒 = 100 秒!
如果先测试第二个条件,那么只会预过滤 5000 个项目(在我的用例中),然后应用第一个条件会非常快。
注:
column2 不是必需的 ID,它可以是其他东西
在我的用例中,myfunction
涉及 Levenshtein 距离计算
(根据评论和后续测试更新了答案。)
您问题的实际答案
how to make sure that, if we have 1 million rows, the query ... is optimized so that the 1st condition (CPU-expensive) is only tested if the easy-to-test second condition is already True?
取决于
- WHERE子句中的实际条件,
- SQLite 查询优化器在估算这些条件的成本方面有多聪明。
一个简单的测试应该可以告诉您您的查询是否足以 "optimized" 满足您的需要。好消息是 SQLite 将 首先执行简单(廉价)条件,至少在某些情况下是这样。
测试table"mytable"
CREATE TABLE mytable (
description TEXT(50) NOT NULL,
column2 INTEGER NOT NULL,
CONSTRAINT mytable_PK PRIMARY KEY (column2)
);
包含一百万行
description column2
----------- -------
row000000 0
row000001 1
row000002 2
...
row999999 999999
Python测试代码
import sqlite3
import time
log_file_spec = r'C:\Users\Gord\Desktop\log_file.txt'
def myfunc(thing):
with open(log_file_spec, 'a') as log:
log.write('HODOR\n')
return(int(thing[-6:]))
with open(log_file_spec, 'w'):
pass # just empty the file
cnxn = sqlite3.connect(r'C:\__tmp\SQLite\test.sqlite')
cnxn.create_function("myfunction", 1, myfunc)
crsr = cnxn.cursor()
t0 = time.time()
sql = """\
SELECT COUNT(*) AS n FROM mytable
WHERE myfunction(description) < 500 AND column2 < 1000
"""
crsr.execute(sql)
num_rows = crsr.fetchone()[0]
print(f"{num_rows} rows found in {(time.time() - t0):.1f} seconds")
cnxn.close()
returns
500 rows found in 1.2 seconds
并计算 log_file.txt 中的行数,我们看到
C:\Users\Gord>find /C "HODOR" Desktop\log_file.txt
---------- DESKTOP\LOG_FILE.TXT: 1000
表明我们的函数只被调用了一千次,而不是一百万次。 SQLite 显然首先应用了 column2 < 1000
,然后在第一个条件的行子集上应用了 myfunction(description) < 500
条件。
(原始 "off the cuff" 答案。)
您问题的实际答案取决于查询优化器的智能程度。一个简单的测试应该可以告诉您您的查询是否足以 "optimized" 满足您的需求。
但是,如果您的测试发现您的原始方法太慢,您确实有几个选择:
选项 1:尝试进行简单比较 "first"
更改顺序可能会影响查询计划,例如
... WHERE <easy_condition> AND <expensive_condition>
可能比
快
... WHERE <expensive_condition> AND <easy_condition>
选项 2:尝试使用子查询强制排序
同样,取决于查询优化器的聪明程度
SELECT easy.*
FROM
(SELECT * FROM mytable WHERE column2 < 1000) easy
WHERE myfunction(easy.description) < 500
可能首先应用廉价条件,然后对生成的行子集应用昂贵条件。 (但是,一条评论表明 SQLite 太复杂了,不能上当。)
强制执行顺序的一种方法是使用 case
表达式。一般来说,SQL 优化器可以重新安排操作,一个例外是 case
.
SELECT *
FROM mytable
WHERE (CASE WHEN column2 >= 1000 OR column2 IS NULL THEN 0
WHEN myfunction(description) < 500 THEN 1
END) = 1;
通常,不鼓励在 WHERE
子句中使用 case
表达式。 . .一个主要原因是它们阻碍了优化。在这种情况下,这是一件好事。
SQLite 会在需要时愉快地重新排序 AND 连接的表达式。因此,虽然重写查询以检查 column2
首先似乎在当前版本中有效,但不能保证。
查询优化器假定速度主要由磁盘决定I/O,因此它估计这两个条件的成本相同。
成本估算受索引和 ANALYZE 统计信息(仅适用于索引数据)的影响。
因此,加速此查询(可能还有您将使用的大多数其他查询)的最简单方法是在 column2
:
上创建索引
CREATE INDEX my_little_index ON mytable(column2);
如果您出于某种原因不想使用索引,则必须使用查询优化器无法优化掉的结构。戈登的回答中所示的 CASE 表达式就可以正常工作。在一般情况下,将第一个条件移动到子查询中,并通过破坏列出的规则之一来防止 subquery flattening;向两个查询添加虚拟 LIMIT 子句通常是最简单的:
SELECT *
FROM (SELECT *
FROM mytable
WHERE column2 < 1000
LIMIT -1)
WHERE myfunction(description) < 500
LIMIT -1;
受@GordThompson 的回答启发,这里有一个基准:
(1) SELECT * FROM mytable WHERE col2 < 1000 AND myfunction(col1) < 500
对比
(2) SELECT * FROM mytable WHERE myfunction(col1) < 500 AND col2 < 1000
测试(一)(易测条件优先):1.02秒
import sqlite3, time, random
def myfunc(x):
time.sleep(0.001) # wait 1 millisecond for each call of this function
return x
# Create database
db = sqlite3.connect(':memory:')
db.create_function("myfunction", 1, myfunc)
c = db.cursor()
c.execute('CREATE TABLE mytable (col1 INTEGER, col2 INTEGER)');
for i in range(10*1000):
a = random.randint(0,1000)
c.execute('INSERT INTO mytable VALUES (?, ?)', (a, i));
# Do the evil query
t0 = time.time()
c.execute('SELECT * FROM mytable WHERE col2 < 1000 AND myfunction(col1) < 500')
for e in c.fetchall():
print e
print "Elapsed time: %.2f" % (time.time() - t0)
结果:1.02 秒,这意味着 myfunc
已被调用最多 1000 次,即 并非所有 10k 行。
测试 (2)(首先是计算缓慢的条件):10.05 秒
同上:
c.execute('SELECT * FROM mytable WHERE myfunction(col1) < 500 AND col2 < 1000')
相反。
结果:10.05 秒,这意味着 myfunc
已被调用 ~ 10k 次,即 对于所有 10k 行 ,即使是那些条件 col2 < 1000
不正确。
全局结论:Sqlite 对 AND
进行惰性求值,即 easy 条件必须先这样写:
... WHERE <easy_condition> AND <expensive_condition>
在 SQL 数据库中(我使用 Python+Sqlite),如果我们有 100 万行,如何确保查询
SELECT * FROM mytable WHERE myfunction(description) < 500 AND column2 < 1000
[-----------------------------] [--------------]
high-CPU cost condition easy-to-test
requiring 100 µs per test condition
已优化,以便第一个条件(CPU-昂贵) 仅在易于测试的第二个条件已经为真时才测试? (既然是合乎逻辑的AND
,是不是懒惰的AND
?)
示例:
如果始终测试第一个条件,则需要 100 万 x 100 微秒 = 100 秒!
如果先测试第二个条件,那么只会预过滤 5000 个项目(在我的用例中),然后应用第一个条件会非常快。
注:
column2 不是必需的 ID,它可以是其他东西
在我的用例中,
myfunction
涉及 Levenshtein 距离计算
(根据评论和后续测试更新了答案。)
您问题的实际答案
how to make sure that, if we have 1 million rows, the query ... is optimized so that the 1st condition (CPU-expensive) is only tested if the easy-to-test second condition is already True?
取决于
- WHERE子句中的实际条件,
- SQLite 查询优化器在估算这些条件的成本方面有多聪明。
一个简单的测试应该可以告诉您您的查询是否足以 "optimized" 满足您的需要。好消息是 SQLite 将 首先执行简单(廉价)条件,至少在某些情况下是这样。
测试table"mytable"
CREATE TABLE mytable (
description TEXT(50) NOT NULL,
column2 INTEGER NOT NULL,
CONSTRAINT mytable_PK PRIMARY KEY (column2)
);
包含一百万行
description column2
----------- -------
row000000 0
row000001 1
row000002 2
...
row999999 999999
Python测试代码
import sqlite3
import time
log_file_spec = r'C:\Users\Gord\Desktop\log_file.txt'
def myfunc(thing):
with open(log_file_spec, 'a') as log:
log.write('HODOR\n')
return(int(thing[-6:]))
with open(log_file_spec, 'w'):
pass # just empty the file
cnxn = sqlite3.connect(r'C:\__tmp\SQLite\test.sqlite')
cnxn.create_function("myfunction", 1, myfunc)
crsr = cnxn.cursor()
t0 = time.time()
sql = """\
SELECT COUNT(*) AS n FROM mytable
WHERE myfunction(description) < 500 AND column2 < 1000
"""
crsr.execute(sql)
num_rows = crsr.fetchone()[0]
print(f"{num_rows} rows found in {(time.time() - t0):.1f} seconds")
cnxn.close()
returns
500 rows found in 1.2 seconds
并计算 log_file.txt 中的行数,我们看到
C:\Users\Gord>find /C "HODOR" Desktop\log_file.txt
---------- DESKTOP\LOG_FILE.TXT: 1000
表明我们的函数只被调用了一千次,而不是一百万次。 SQLite 显然首先应用了 column2 < 1000
,然后在第一个条件的行子集上应用了 myfunction(description) < 500
条件。
(原始 "off the cuff" 答案。)
您问题的实际答案取决于查询优化器的智能程度。一个简单的测试应该可以告诉您您的查询是否足以 "optimized" 满足您的需求。
但是,如果您的测试发现您的原始方法太慢,您确实有几个选择:
选项 1:尝试进行简单比较 "first"
更改顺序可能会影响查询计划,例如
... WHERE <easy_condition> AND <expensive_condition>
可能比
快... WHERE <expensive_condition> AND <easy_condition>
选项 2:尝试使用子查询强制排序
同样,取决于查询优化器的聪明程度
SELECT easy.*
FROM
(SELECT * FROM mytable WHERE column2 < 1000) easy
WHERE myfunction(easy.description) < 500
可能首先应用廉价条件,然后对生成的行子集应用昂贵条件。 (但是,一条评论表明 SQLite 太复杂了,不能上当。)
强制执行顺序的一种方法是使用 case
表达式。一般来说,SQL 优化器可以重新安排操作,一个例外是 case
.
SELECT *
FROM mytable
WHERE (CASE WHEN column2 >= 1000 OR column2 IS NULL THEN 0
WHEN myfunction(description) < 500 THEN 1
END) = 1;
通常,不鼓励在 WHERE
子句中使用 case
表达式。 . .一个主要原因是它们阻碍了优化。在这种情况下,这是一件好事。
SQLite 会在需要时愉快地重新排序 AND 连接的表达式。因此,虽然重写查询以检查 column2
首先似乎在当前版本中有效,但不能保证。
查询优化器假定速度主要由磁盘决定I/O,因此它估计这两个条件的成本相同。
成本估算受索引和 ANALYZE 统计信息(仅适用于索引数据)的影响。
因此,加速此查询(可能还有您将使用的大多数其他查询)的最简单方法是在 column2
:
CREATE INDEX my_little_index ON mytable(column2);
如果您出于某种原因不想使用索引,则必须使用查询优化器无法优化掉的结构。戈登的回答中所示的 CASE 表达式就可以正常工作。在一般情况下,将第一个条件移动到子查询中,并通过破坏列出的规则之一来防止 subquery flattening;向两个查询添加虚拟 LIMIT 子句通常是最简单的:
SELECT *
FROM (SELECT *
FROM mytable
WHERE column2 < 1000
LIMIT -1)
WHERE myfunction(description) < 500
LIMIT -1;
受@GordThompson 的回答启发,这里有一个基准:
(1) SELECT * FROM mytable WHERE col2 < 1000 AND myfunction(col1) < 500
对比
(2) SELECT * FROM mytable WHERE myfunction(col1) < 500 AND col2 < 1000
测试(一)(易测条件优先):1.02秒
import sqlite3, time, random
def myfunc(x):
time.sleep(0.001) # wait 1 millisecond for each call of this function
return x
# Create database
db = sqlite3.connect(':memory:')
db.create_function("myfunction", 1, myfunc)
c = db.cursor()
c.execute('CREATE TABLE mytable (col1 INTEGER, col2 INTEGER)');
for i in range(10*1000):
a = random.randint(0,1000)
c.execute('INSERT INTO mytable VALUES (?, ?)', (a, i));
# Do the evil query
t0 = time.time()
c.execute('SELECT * FROM mytable WHERE col2 < 1000 AND myfunction(col1) < 500')
for e in c.fetchall():
print e
print "Elapsed time: %.2f" % (time.time() - t0)
结果:1.02 秒,这意味着 myfunc
已被调用最多 1000 次,即 并非所有 10k 行。
测试 (2)(首先是计算缓慢的条件):10.05 秒
同上:
c.execute('SELECT * FROM mytable WHERE myfunction(col1) < 500 AND col2 < 1000')
相反。
结果:10.05 秒,这意味着 myfunc
已被调用 ~ 10k 次,即 对于所有 10k 行 ,即使是那些条件 col2 < 1000
不正确。
全局结论:Sqlite 对 AND
进行惰性求值,即 easy 条件必须先这样写:
... WHERE <easy_condition> AND <expensive_condition>