SQLite是否在WHERE子句中优化具有多个AND条件的查询?

时间:2018-04-16 15:54:39

标签: python sql sqlite lazy-evaluation

在SQL数据库(我使用Python + Sqlite)中,如果我们有100万行,如何确保查询

AND
如果易于测试的第二个条件已经为True,

是否已经过优化,以便第一个条件(CPU昂贵)仅经过测试? (因为它是一个逻辑AND,它是一个懒惰的myfunction?)

示例:

  • 如果始终测试第一个条件,则需要100万x100μs= 100秒!

  • 如果首先测试第二个条件,那么只有5000个项目会被预过滤(在我的用例中),然后,应用第一个条件会非常快。

注意:

  • 第2列不是必需的ID,也可能是其他内容

  • 在我的用例中,{{1}}涉及Levenshtein距离计算

4 个答案:

答案 0 :(得分:3)

您可以强制执行顺序的一种方法是使用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;

通常,case子句中不鼓励WHERE个表达式。 。 。一个主要原因是它们阻碍了优化。在这种情况下,这是一件好事。

答案 1 :(得分:2)

(根据评论和后续测试更新了答案。)

您问题的实际答案

  

如何确保,如果我们有100万行,则查询...已经过优化,以便只有在易于测试的第二个条件已经为True时才会测试第一个条件(CPU昂贵)? / p>

取决于

  • WHERE子句中的实际条件和
  • SQLite查询优化器在估计这些条件的成本方面有多聪明。

一个简单的测试应该告诉你你的查询是否足够&#34;优化&#34;满足您的需求。好消息是SQLite 首先执行简单(廉价)的条件,至少在某些情况下。

对于测试表&#34; mytable&#34;

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()

返回

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条件。

<小时/> (原创&#34;关闭袖口&#34;答案。)

问题的实际答案取决于查询优化器的巧妙程度。一个简单的测试应该告诉你你的查询是否足够&#34;优化&#34;满足您的需求。

但是,如果您的测试发现您的原始方法太慢,您确实有几个选项:

选项1:尝试进行简单比较&#34;首先&#34;

更改订单可能会影响查询计划,例如

... 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太复杂了,不适合这种策略。)

答案 2 :(得分:2)

SQLite会很高兴地重新排序AND连接的表达式。因此,在重写查询以检查column2时,首先看起来在当前版本中有效,但无法保证。

查询优化器假设速度主要由磁盘I / O决定,因此它估计两个条件的成本是相同的。 成本估算受索引和ANALYZE统计信息(仅适用于索引数据)的影响。 因此,加速此查询的最简单方法(可能是您将使用的大多数其他查询)是在column2上创建索引:

CREATE INDEX my_little_index ON mytable(column2);

如果由于某种原因不想使用索引,则必须使用查询优化器无法优化的构造。 Gordon的答案中显示的CASE表达式可以正常工作。在一般情况下,将第一个条件移动到子查询中,并通过破坏列出的规则之一来阻止subquery flattening;为两个查询添加一个伪LIMIT子句通常是最简单的:

SELECT *
FROM (SELECT *
      FROM mytable
      WHERE column2 < 1000
      LIMIT -1)
WHERE myfunction(description) < 500
LIMIT -1;

答案 3 :(得分:0)

受@ GordThompson的回答启发,这是以下之间的基准:

(1)  SELECT * FROM mytable WHERE col2 < 1000 AND myfunction(col1) < 500

VS

(2)  SELECT * FROM mytable WHERE myfunction(col1) < 500 AND col2 < 1000

测试(1)(首先是易于测试的条件):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')

代替。

结果:1​​0.05秒,表示myfunc已被调用~10k次,即所有10k行的,即使条件col2 < 1000不是真。

全局结论: Sqlite对AND进行了懒惰评估,即必须首先写出这样的简单条件:

... WHERE <easy_condition> AND <expensive_condition>