SQLAlchemy in 查询空列表问题分析

栏目: Python · 发布时间: 6年前

内容简介:有model这里 uids 如果为空,执行查询会有如下警告:这里的意思是使用一个空的列表会花费较长的时间,需要优化以提高性能。

有model Account ,SQLAlchemy 查询语句如下:

query = Account.query.filter(Account.id.in_(account_ids)).order_by(Account.date_created.desc())
复制代码

这里 uids 如果为空,执行查询会有如下警告:

/usr/local/lib/python2.7/site-packages/sqlalchemy/sql/default_comparator.py:35: SAWarning: The IN-predicate on "account.id" was invoked with an empty sequence. This results in a contradiction, which nonetheless can be expensive to evaluate.  Consider alternative strategies for improved performance.
  return o[0](self, self.expr, op, *(other + o[1:]), **kwargs)
复制代码

这里的意思是使用一个空的列表会花费较长的时间,需要优化以提高性能。

为什么会有这个提示呢?一个空列表为什么会影响性能呢?

首先打印 query 可得到如下 sql 语句:

SELECT *   // 字段使用 “*” 代替
FROM account
WHERE account.id != account.id ORDER BY account.date_created DESC
复制代码

会发现生成的语句中过滤条件是 WHERE account.id != account.id ,使用 PostgreSQL Explain ANALYZE 命令

  • EXPLAIN:显示PostgreSQL计划程序为提供的语句生成的执行计划。
  • ANALYZE:收集有关数据库中表的内容的统计信息。

分析查询成本结果如下:

postgres=> EXPLAIN ANALYZE SELECT *
FROM account
WHERE account.id != account.id ORDER BY account.date_created DESC;
                                    QUERY PLAN
----------------------------------------------------------------------------------
 Sort  (cost=797159.14..808338.40 rows=4471702 width=29) (actual time=574.002..574.002 rows=0 loops=1)
   Sort Key: date_created DESC
   Sort Method: quicksort  Memory: 25kB
   ->  Seq Scan on account  (cost=0.00..89223.16 rows=4471702 width=29) (actual time=573.991..573.991 rows=0 loops=1)
         Filter: (id <> id)
         Rows Removed by Filter: 4494173
 Planning time: 0.162 ms
 Execution time: 574.052 ms
(8 rows)
复制代码

先看Postgresql提供的语句生成的执行计划,通过结果可以看到,虽然返回值为空,但是查询成本却还是特别高,执行计划部分几乎所有的时间都耗费在 排序 上,但是和执行时间相比,查询计划的时间可以忽略不计。(结果是先遍历全表,查出所有数据,然后再使用 Filter: (id <> id) 把所有数据过滤。)

按照这个思路,有两种查询方案:

  1. 如果 account_ids 为空,那么直接返回空列表不进行任何操作,查询语句变为:
if account_ids:
    query = Account.query.filter(Account.id.in_(account_ids)).order_by(Account.date_created.desc())
复制代码
  1. 如果 account_ids 为空,那么过滤方式,查询语句变为:
query = Account.query
if account_ids:
    query = query.filter(Account.id.in_(account_ids))
else:
    query = query.filter(False)
    
query = query.order_by(Account.date_created.desc())
复制代码

如果 account_ids 为空,此时生成的 SQL 语句结果为:

SELECT *
FROM account
WHERE 0 = 1 ORDER BY account.date_created DESC
复制代码

分析结果为:

postgres=> EXPLAIN ANALYZE SELECT *
FROM account
WHERE 0 = 1 ORDER BY account.date_created DESC;
                                            QUERY PLAN
---------------------------------------------------------------------------------------------------
 Sort  (cost=77987.74..77987.75 rows=1 width=29) (actual time=0.011..0.011 rows=0 loops=1)
   Sort Key: date_created DESC
   Sort Method: quicksort  Memory: 25kB
   ->  Result  (cost=0.00..77987.73 rows=1 width=29) (actual time=0.001..0.001 rows=0 loops=1)
         One-Time Filter: false
         ->  Seq Scan on account  (cost=0.00..77987.73 rows=1 width=29) (never executed)
 Planning time: 0.197 ms
 Execution time: 0.061 ms
(8 rows)
复制代码

可以看到,查询计划和执行时间都有大幅提高。

一个测试

如果只是去掉方案1排序,查看一下分析结果

使用 PostgreSQL Explain ANALYZE 命令 分析查询成本结果如下:

postgres=> EXPLAIN ANALYZE SELECT *
FROM account
WHERE account.id != account.id;
                                 QUERY PLAN
----------------------------------------------------------------------------
 Seq Scan on account  (cost=0.00..89223.16 rows=4471702 width=29) (actual time=550.999..550.999 rows=0 loops=1)
   Filter: (id <> id)
   Rows Removed by Filter: 4494173
 Planning time: 0.134 ms
 Execution time: 551.041 ms
复制代码

可以看到,时间和有排序时差别不大。

如何计算查询成本

执行一个分析,结果如下:

postgres=> explain select * from account where date_created ='2016-04-07 18:51:30.371495+08';
                                      QUERY PLAN
--------------------------------------------------------------------------------------
 Seq Scan on account  (cost=0.00..127716.33 rows=1 width=211)
   Filter: (date_created = '2016-04-07 18:51:30.371495+08'::timestamp with time zone)
(2 rows)
复制代码

EXPLAIN引用的数据是:

  1. 0.00 预计的启动开销(在输出扫描开始之前消耗的时间,比如在一个排序节点里做排续的时间)。
  2. 127716.33 预计的总开销。
  3. 1 预计的该规划节点输出的行数。
  4. 211 预计的该规划节点的行平均宽度(单位:字节)。

这里开销(cost)的计算单位是磁盘页面的存取数量,如1.0将表示一次顺序的磁盘页面读取。其中上层节点的开销将包括其所有子节点的开销。这里的输出行数(rows)并不是规划节点处理/扫描的行数,通常会更少一些。一般而言,顶层的行预计数量会更接近于查询实际返回的行数。 这里表示的就是在只有单 CPU 内核的情况下,评估成本是127716.33;

计算成本,Postgresql 首先看表的字节数大小

这里 account 表的大小为:

postgres=> select pg_relation_size('account');

pg_relation_size
------------------
        737673216
(1 row)
复制代码

查看块的大小

Postgresql 会为每个要一次读取的快添加成本点,使用 show block_size 查看块的大小:

postgres=> show block_size;

block_size
------------
 8192
(1 row)
复制代码

计算块的个数

可以看到每个块的大小为8kb,那么可以计算从表从读取的顺序块成本值为:

blocks = pg_relation_size/block_size = 90048
复制代码

90048 是account 表所占用块的数量。

查看每个块需要的成本

postgres=> show seq_page_cost;
 seq_page_cost
---------------
 1
(1 row)
复制代码

这里的意思是 Postgresql 为每个块分配一个成本点,也就是说上面的查询需要从90048个成本点。

处理每条数据 cpu 所需时间

  • cpu_tuple_cost:处理每条记录的CPU开销(tuple:关系中的一行记录)
  • cpu_operator_cost:操作符或函数带来的CPU开销。
postgres=> show cpu_operator_cost;
 cpu_operator_cost
-------------------
 0.0025
(1 row)

postgres=> show cpu_tuple_cost;
 cpu_tuple_cost
----------------
 0.01
(1 row)
复制代码

计算

cost 计算公式为:

cost = 磁盘块个数 * 块成本(1) + 行数 * cpu_tuple_cost(系统参数值)+ 行数 * cpu_operator_cost

现在用所有值来计算explain 语句中得到的值:

number_of_records = 3013466  # account 表 count

block_size = 8192  # block size in bytes

pg_relation_size=737673216

blocks = pg_relation_size/block_size = 90048

seq_page_cost = 1
cpu_tuple_cost = 0.01
cpu_operator_cost = 0.0025

cost = blocks * seq_page_cost + number_of_records * cpu_tuple_cost + number_of_records * cpu_operator_cost
复制代码

如何降低查询成本?

直接回答,使用索引。

postgres=> explain select * from account where id=20039;
                                       QUERY PLAN
----------------------------------------------------------------------------------------
 Index Scan using account_pkey on account  (cost=0.43..8.45 rows=1 width=211)
   Index Cond: (id = 20039)
(2 rows)
复制代码

通过这个查询可以看到,在使用有索引的字段查询时,查询成本显著降低。

索引扫描的计算比顺序扫描的计算要复杂一些。它由两个阶段组成。 PostgreSQL会考虑random_page_cost和cpu_index_tuple_cost 变量,并返回一个基于索引树的高度的值。

参考链接


以上所述就是小编给大家介绍的《SQLAlchemy in 查询空列表问题分析》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

3D游戏设计与开发

3D游戏设计与开发

2011-9 / 28.00元

《3D游戏设计与开发》,本书共分为九章:第一章主要对计算机游戏设计和3D游戏引擎进行简单介绍;第二章介绍3D游戏开发基础和Torque引擎的各种对象;第三章详细讲解游戏编程的语言及其语法;第四章详细介绍了Torque引擎编辑器的应用;第五章至第六章介绍了3D游戏的环境、角色和物品的制作;第七章讲解如何实现游戏音效;第八章详细介绍3D网络游戏的创建方法;第九章讲解如何掌握3D资源导入Torque引擎......一起来看看 《3D游戏设计与开发》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

MD5 加密
MD5 加密

MD5 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具