【问题标题】:Linq SQL generation for pagination用于分页的 Linq SQL 生成
【发布时间】:2018-03-02 22:30:10
【问题描述】:

使用 EF6、ODP.NET 12.2 和 Oracle 12.2 服务器,我有下表:

id  raw(16)
number  number(19)
name    varchar2(100 char) not null
description varchar2(100 char) not null

(名称)和(名称、描述)的索引

以下 LINQ 查询:

db.SAMPLES.OrderBy(p => p.NAME).ThenBy(p => p.DESCRIPTION).Skip(0).Take(10).ToList();

产生这个 SQL:

SELECT * 
 FROM ( 
 SELECT 
 "Extent1"."ID" AS "ID", 
 "Extent1"."NUMBER" AS "NUMBER", 
 "Extent1"."NAME" AS "NAME"
 FROM ( SELECT "Extent1"."ID" AS "ID", "Extent1"."NUMBER" AS "NUMBER", "Extent1"."NAME" AS "NAME", row_number() OVER (ORDER BY "Extent1"."NAME" ASC) AS "row_number"
  FROM "ZENKI"."SAMPLEs" "Extent1"
 )  "Extent1"
 WHERE ("Extent1"."row_number" > 0)
 ORDER BY "Extent1"."NAME" ASC
 )
 WHERE (ROWNUM <= (10) )

查询运行良好,但性能不佳。显然,由于 (ORDER BY "Extent1"."NAME" ASC) 的(额外??)顺序,Oracle 没有使用索引。

查询的执行计划是:

----------------------------------------------------------------------------------------------
| Id  | Operation                          | Name      | Rows   | Bytes    | Cost | Time     |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                   |           |     10 |     2250 |    2 | 00:00:01 |
| * 1 |   COUNT STOPKEY                    |           |        |          |      |          |
|   2 |    VIEW                            |           |     10 |     2250 |    2 | 00:00:01 |
| * 3 |     SORT ORDER BY STOPKEY          |           |     10 |     2380 |    2 | 00:00:01 |
| * 4 |      VIEW                          |           |     10 |     2380 |    1 | 00:00:01 |
|   5 |       WINDOW NOSORT                |           |     10 |     1300 |    1 | 00:00:01 |
|   6 |        TABLE ACCESS BY INDEX ROWID | SAMPLEs   | 245376 | 31898880 |    1 | 00:00:01 |
|   7 |         INDEX FULL SCAN            | INDEX_SP1 |     10 |          |    1 | 00:00:01 |
----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 1 - filter(ROWNUM<=10)
* 3 - filter(ROWNUM<=10)
* 4 - filter("Extent1"."row_number">0)

如果我删除最后一个订单,查询会很快并产生相同的结果。而执行计划变成:

--------------------------------------------------------------------------------------------
| Id  | Operation                        | Name      | Rows   | Bytes    | Cost | Time     |
--------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                 |           |     10 |     2380 |    1 | 00:00:01 |
| * 1 |   COUNT STOPKEY                  |           |        |          |      |          |
| * 2 |    VIEW                          |           |     10 |     2380 |    1 | 00:00:01 |
|   3 |     WINDOW NOSORT                |           |     10 |     1300 |    1 | 00:00:01 |
|   4 |      TABLE ACCESS BY INDEX ROWID | SAMPLEs   | 245376 | 31898880 |    1 | 00:00:01 |
|   5 |       INDEX FULL SCAN            | INDEX_SP1 |     10 |          |    1 | 00:00:01 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
------------------------------------------
* 1 - filter(ROWNUM<=10)
* 2 - filter("Extent1"."row_number">0)

经过大量谷歌搜索后,我在 ODP.NET 论坛中询问并得到了这个:

https://community.oracle.com/thread/4126265

似乎 odp.net 无法控制(或限制)生成的 SQL。我认为提供程序从表达式树生成 SQL,那么这怎么可能呢?有其他人对这种类型的查询有这个问题吗?

更新:

LINQ 论坛中的帖子:

https://social.msdn.microsoft.com/Forums/en-US/b6174ef9-a832-45da-8345-b97441d40363/linq-query-pagination-sql-generation?forum=linqtosql

更新 1:

使用 Number 数据类型而不是 varchar 作为 order by 会产生不同的解释计划并且非常快,所以我开始认为这与数据类型有任何关系。

【问题讨论】:

  • 由于IQueryable没有AsUnordered,你可以尝试在take后添加.Distinct()。
  • 相同的性能结果
  • 正如您在 ODP.NET 论坛中看到的那样,这是使用 ORM 的痛点之一......潜在的低效 SQL。像这样的问题总是会不断出现,可能是由于 EF 错误或 ODP.NET 错误。 APPLY 关键字是 EF 历史上最严重的违规者。多年来,只有当表达式树指示需要“APPLY”时,SQL Server 才能工作。不仅是 Oracle,没有其他数据库可以处理这种情况。最后 MS 所需的数据库添加 APPLY 关键字或抛出错误。
  • @ChristianShay 我不能理解你的意思,真正困扰我的是这个查询似乎很常见。没有人尝试使用 EF 和 Oracle 使用分页查询?
  • 这可能是 ODP.NET 中的错误,也可能不是。问题是,错误得到修复,然后表达式树在未来的 EF 版本中发生变化,并暴露出另一个不同的错误或效率低下。我只是指出 ORM 的一个弱点。

标签: oracle performance linq odp.net


【解决方案1】:

恐怕我帮不上忙,我不使用你做的软件。不过,由于没有人在 12 小时内回复,而且我有一些空闲时间,所以我想说几句话。

以下 LINQ 查询 (...) 生成此 SQL (...)

(稍微格式化以提高可读性;我已经标记了查询的 A、B 和 C 部分,稍后将讨论):

-- C
SELECT * 
 FROM (-- B
       SELECT 
          "Extent1"."ID" AS "ID", 
          "Extent1"."NUMBER" AS "NUMBER", 
          "Extent1"."NAME" AS "NAME"
       FROM (-- A
             SELECT "Extent1"."ID" AS "ID", 
                    "Extent1"."NUMBER" AS "NUMBER", 
                    "Extent1"."NAME" AS "NAME", 
                    row_number() OVER (ORDER BY "Extent1"."NAME" ASC) AS "row_number"
             FROM "ZENKI"."SAMPLEs" "Extent1"
            )  "Extent1"
       WHERE ("Extent1"."row_number" > 0)
       ORDER BY "Extent1"."NAME" ASC
      )
 WHERE (ROWNUM <= (10) );

如果我们从内部,A查询,这里有一些cmets:

答: ROW_NUMBER“排名”按 NAME ASC 排序的行。没关系。

SELECT "Extent1"."ID" AS "ID", 
       "Extent1"."NUMBER" AS "NUMBER", 
       "Extent1"."NAME" AS "NAME", 
       row_number() OVER (ORDER BY "Extent1"."NAME" ASC) AS "row_number"
  FROM "ZENKI"."SAMPLEs" "Extent1"

B: WHERE 子句是不必要的; ROW_NUMBER 总是 > 0。ORDER BY 也是不必要的,因为 A 查询中使用了 ROW_NUMBER,这使得整个 B 查询成为多余的。

select id, number, name
from A
where row_number > 0
ORDER BY NAME asc

C: 如果 A 查询没有生成 ROW_NUMBER,WHERE 子句才有意义;使用其中一个,而不是两个

select *
from B
where rownum <= 10

基本上,这也可以完成这项工作:

select *
from A
where row_number <= 10

即如果你能让 LINQ 产生这样的东西,性能也可能会受益。

select *
from (SELECT "Extent1"."ID" AS "ID", 
        "Extent1"."NUMBER" AS "NUMBER", 
        "Extent1"."NAME" AS "NAME", 
        row_number() OVER (ORDER BY "Extent1"."NAME" ASC) AS "row_number"
      FROM "ZENKI"."SAMPLEs" "Extent1" 
    )
where row_number <= 10

只是好奇:如果你运行它,它的性能如何?它是否返回所需的结果?如果答案是“好且是”,也许您可​​以尝试说服 LINQ 生成不同的查询。

【讨论】:

  • 运行速度非常快。原始 SQL 生成的查询已经以闪电般的速度移除了额外的 order by。页面的其余部分需要您删除的附加 where 子句(这是一个分页查询)。问题是,如何让 LINQ 生成更好的查询,或者如何改变原来的 LINQ 跳过查询的顺序以生成更好的 SQL。
  • 不幸的是,我不知道 :( 我希望您可以设置 something 并使 LINQ 产生“更智能”的查询。希望其他人会能够提供帮助。祝你好运!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多