带order_by和分页的Flask-SqlAlchemy查询非常慢

Tos*_*osh 5 postgresql pagination sqlalchemy sql-order-by flask

我使用Python = 2.7.3,PostgreSQL = 9.3,Flask = 0.10.0,SQLAlchemy = 1.10.12,Flask-SQLAlchemy = 2.1和psycopg2 = 2.5.4

我在PostgreSQL中有一个10亿个记录表,我必须对它进行分页并在其中进行搜索:

class MyTable(db.Model):
    """ My Table """
    id = db.Column(db.Integer, primary_key=True)
    code = db.Column(db.String(100), index=True, unique=True)
    name = db.Column(db.String(512), index=True)
    __tablename__ = 'my_table'
Run Code Online (Sandbox Code Playgroud)

所以我在请求数据的代码中做了以下操作:

records = MyTable.query.filter(**filter_list).\
    order_by(asc('code')).paginate(page, per_page, False)
Run Code Online (Sandbox Code Playgroud)

关键是,如果per_page = 10和page = 1158960,即使根本没有过滤,也只需要花13秒就可以为最后一页选择10条最后一条记录。

从我在flask- sqlalchemy来源中发现的内容中,.paginate如下所示:

.order_by(asc('code')).limit(per_page).offset((page - 1) * per_page)
Run Code Online (Sandbox Code Playgroud)

产生的SQL查询如下所示:

SELECT my_table.id, my_table.code, my_table.name 
FROM my_table ORDER BY my_table.code ASC 
LIMIT 10 OFFSET 1158960
Run Code Online (Sandbox Code Playgroud)

当我在服务器控制台上将其触发时,我意识到问题出在ORDER BY子句中。它必须以某种方式首先使用ORDER BY对整个表进行排序,然后再对LIMITOFFSET进行排序。但这慢得要命。

说明(分析):

"Limit  (cost=470520.26..470520.26 rows=1 width=178) (actual time=12460.060..12460.061 rows=8 loops=1)"
"  ->  Sort  (cost=467626.96..470520.26 rows=1157320 width=178) (actual time=11343.220..12424.686 rows=1158968 loops=1)"
"        Sort Key: code"
"        Sort Method: external merge  Disk: 218312kB"
"        ->  Seq Scan on my_table  (cost=0.00..42518.20 rows=1157320 width=178) (actual time=0.026..378.637 rows=1158968 loops=1)"
"Total runtime: 12475.160 ms"
Run Code Online (Sandbox Code Playgroud)

如果仅从该SQL请求中删除ORDER BY,则它会在270毫秒内完成!

"Limit  (cost=42518.20..42518.20 rows=1 width=178) (actual time=269.940..269.942 rows=8 loops=1)"
"  ->  Seq Scan on my_table  (cost=0.00..42518.20 rows=1157320 width=178) (actual time=0.030..246.200 rows=1158968 loops=1)"
"Total runtime: 269.992 ms"
Run Code Online (Sandbox Code Playgroud)

有什么我可以做的吗?

Tos*_*osh 2

好吧,我找到了解决这个问题的方法。

当我执行完全相同的查询但使用SET enable_seqscan=off; 它强制 PostgreSQL 使用索引扫描而不是序列扫描,并且速度变得更快

SET enable_seqscan=off;
SELECT my_table.id, my_table.code, my_table.name 
FROM my_table ORDER BY my_table.code ASC 
LIMIT 10 OFFSET 1158960

**EXPLAIN (ANALYZE):**

"Limit  (cost=1814764.86..1814777.39 rows=8 width=131) (actual time=616.543..616.545 rows=8 loops=1)"
"  ->  Index Scan using ix_my_table_code on my_table  (cost=0.43..1814777.39 rows=1158968 width=131) (actual time=0.065..590.898 rows=1158968 loops=1)"
"Total runtime: 616.568 ms"
Run Code Online (Sandbox Code Playgroud)

所以现在的重点是 - 如何设置 PostgreSQL 配置以使其使用索引扫描而不强制它?我猜答案是——“规划成本常数”。对他们有什么建议吗?

2016 年 4 月 13 日更新:

我终于弄清楚了情况并找到了解决办法。就我而言,一切都可以通过在postgresql.conf中设置Planner Cost Constants来解决:

seq_page_cost = 1.0
random_page_cost = 1.0
cpu_tuple_cost = 0.01
cpu_index_tuple_cost = 0.0001
cpu_operator_cost = 0.0025
effective_cache_size = 1024MB
Run Code Online (Sandbox Code Playgroud)

此外,有很多建议将 effective_cache_size 设置为服务器整个 RAM 的 3/4。无论如何,通过这些设置,Planner 始终对大型表使用索引扫描。所以计时现在是 200-300ms。

问题解决了。