【问题标题】:MySQL Entity Framework Wraps query into sub-select for Order ByMySQL Entity Framework 将查询包装到 Order By 的子选择中
【发布时间】:2016-04-05 18:58:53
【问题描述】:

对于 MVC 5 应用程序中的 Entityframework 6,我们同时支持 MSSQL 和 MySQL。现在,我遇到的问题是在使用 MySQL 连接器和 LINQ 时,具有 INNER JOIN 和 ORDER BY 的查询将导致查询被带入子选择,并且 ORDER BY 应用于外部。这会导致严重的性能影响。使用 MSSQL 连接器时不会发生这种情况。这是一个例子:

SELECT 
    `Project3`.*
FROM
    (SELECT 
        `Extent1`.*,
            `Extent2`.`Name_First`
    FROM
        `ResultRecord` AS `Extent1`
    LEFT OUTER JOIN `ResultInputEntity` AS `Extent2` ON `Extent1`.`Id` = `Extent2`.`Id`
    WHERE
        `Extent1`.`DateCreated` <= '4/4/2016 6:29:59 PM'
            AND `Extent1`.`DateCreated` >= '12/31/2015 6:30:00 PM'
            AND 0000 = `Extent1`.`CustomerId`
            AND (`Extent1`.`InUseById` IS NULL OR 0000 = `Extent1`.`InUseById` OR `Extent1`.`LockExpiration` < '4/4/2016 6:29:59 PM')
            AND `Extent1`.`DivisionId` IN (0000)
            AND `Extent1`.`IsDeleted` != 1
            AND EXISTS( SELECT 
                1 AS `C1`
            FROM
                `ResultInputEntityIdentification` AS `Extent3`
            WHERE
                `Extent1`.`Id` = `Extent3`.`InputEntity_Id`
                    AND 0 = `Extent3`.`Type`
                    AND '0000' = `Extent3`.`Number`
                    AND NOT (`Extent3`.`Number` IS NULL)
                    OR LENGTH(`Extent3`.`Number`) = 0)
            AND EXISTS( SELECT 
                1 AS `C1`
            FROM
                `ResultRecordAssignment` AS `Extent4`
            WHERE
                1 = `Extent4`.`AssignmentType`
                    AND `Extent4`.`AssignmentId` = 0000
                    OR 2 = `Extent4`.`AssignmentType`
                    AND `Extent4`.`AssignmentId` = 0000
                    AND `Extent4`.`ResultRecordId` = `Extent1`.`Id`)) AS `Project3`
ORDER BY `Project3`.`DateCreated` ASC , `Project3`.`Name_First` ASC , `Project3`.`Id` ASC
LIMIT 0 , 25

当针对几百万行运行此查询时,它只会超时。这是上述查询的解释:

    | id | select_type        | table   | type | possible_keys                                                                                                                                                        | key                        | key_len | ref        | rows     | extra                                        |
    |  1 | PRIMARY            | Extent1 | ref  | IX_ResultRecord_CustomerId,IX_ResultRecord_DateCreated,IX_ResultRecord_IsDeleted,IX_ResultRecord_InUseById,IX_ResultRecord_LockExpiration,IX_ResultRecord_DivisionId | IX_ResultRecord_CustomerId | 4       | const      | 1        | Using where; Using temporary; Using filesort |
    |  1 | PRIMARY            | Extent2 | ref  | PRIMARY                                                                                                                                                              | PRIMARY                    | 8       | Extent1.Id | 1        |                                              |
    |  4 | DEPENDENT SUBQUERY | Extent4 | ref  | IX_RA_AT,IX_RA_A_ID,IX_RA_RR_ID                                                                                                                                      | IX_RA_A_ID                 | 5       | const      | 1        | Using where                                  |
    |  3 | DEPENDENT SUBQUERY | Extent3 | ALL  | IX_InputEntity_Id,IX_InputEntityIdentification_Type,IX_InputEntityIdentification_Number                                                                              |                            |         |            | 14341877 | Using where 

现在,因为它会在 MSSQL 中生成,或者我们只是去掉对 ORDER BY 的子选择,所以改进是巨大的!

SELECT 
    `Extent1`.*, 
    `Extent2`.`Name_First`
FROM
    `ResultRecord` AS `Extent1`
    LEFT OUTER JOIN `ResultInputEntity` AS `Extent2` ON `Extent1`.`Id` = `Extent2`.`Id`
WHERE
    `Extent1`.`DateCreated` <= '4/4/2016 6:29:59 PM'
        AND `Extent1`.`DateCreated` >= '12/31/2015 6:30:00 PM'
        AND 0000 = `Extent1`.`CustomerId`
        AND (`Extent1`.`InUseById` IS NULL
        OR 0000 = `Extent1`.`InUseById`
        OR `Extent1`.`LockExpiration` < '4/4/2016 6:29:59 PM')
        AND `Extent1`.`DivisionId` IN (0000)
        AND `Extent1`.`IsDeleted` != 1
        AND EXISTS( SELECT 
            1 AS `C1`
        FROM
            `ResultInputEntityIdentification` AS `Extent3`
        WHERE
            `Extent1`.`Id` = `Extent3`.`InputEntity_Id`
                AND 9 = `Extent3`.`Type`
                AND '0000' = `Extent3`.`Number`
                AND NOT (`Extent3`.`Number` IS NULL)
                OR LENGTH(`Extent3`.`Number`) = 0)
        AND EXISTS( SELECT 
            1 AS `C1`
        FROM
            `ResultRecordAssignment` AS `Extent4`
        WHERE
            1 = `Extent4`.`AssignmentType`
                AND `Extent4`.`AssignmentId` = 0000
                OR 2 = `Extent4`.`AssignmentType`
                AND `Extent4`.`AssignmentId` = 0000
                AND `Extent4`.`ResultRecordId` = `Extent1`.`Id`)
ORDER BY `Extent1`.`DateCreated` ASC , `Extent2`.`Name_First` ASC , `Extent1`.`Id` ASC
LIMIT 0 , 25

这个查询现在在 0.10 秒内运行!现在的解释计划是这样的:

| id | select_type  | table       | type | possible_keys                                                                                                                                                                | key                               | key_len | ref                    | rows | extra                                       |
|  1 | PRIMARY      | <subquery2> | ALL  | distinct_key                                                                                                                                                                 |                                   |         |                        | 1    | Using temporary; Using filesort             |
|  1 | PRIMARY      | Extent1     | ref  | PRIMARY,IX_ResultRecord_CustomerId,IX_ResultRecord_DateCreated,IX_ResultRecord_IsDeleted,IX_ResultRecord_InUseById,IX_ResultRecord_LockExpiration,IX_ResultRecord_DivisionId | PRIMARY                           | 8       | Extent3.InputEntity_Id | 1    | Using where                                 |
|  1 | PRIMARY      | Extent4     | ref  | IX_RA_AT,IX_RA_A_ID,IX_RA_RR_ID                                                                                                                                              | IX_RA_RR_ID                       | 8       | Extent3.InputEntity_Id | 1    | Using where; Start temporary; End temporary |
|  1 | PRIMARY      | Extent2     | ref  | PRIMARY                                                                                                                                                                      | PRIMARY                           | 8       | Extent3.InputEntity_Id | 1    |                                             |
|  2 | MATERIALIZED | Extent3     | ref  | IX_InputEntity_Id,IX_InputEntityIdentification_Type,IX_InputEntityIdentification_Number                                                                                      | IX_InputEntityIdentification_Type | 4       | const                  | 1    | Using where                                 |

现在,我在整个系统中多次遇到此问题,很明显,这是 MySQL EF 6 连接器决定始终将查询包装在子选择中以应用 ORDER BY 的问题,但仅当查询中有一个联接。这导致了主要的性能问题。我见过的一些答案建议修改连接器源代码,但这可能很乏味,有没有人遇到过同样的问题,知道解决方法,已经修改了连接器,或者除了简单地迁移到 SQL Server 并留下 MySQL 之外还有其他建议,因为那不是一个选项。

【问题讨论】:

  • 不是一个不错的选择,但看起来您应该在内存中进行排序。并在 MySQL Connector 提交一个确定的功能请求。
  • 这是有问题的,主要是因为这些请求可以带回的数据量。我肯定会提交功能请求。谢谢!

标签: mysql sql asp.net-mvc entity-framework


【解决方案1】:

您看过 SQL Server 生成的 SQL 吗?是不同还是只是性能不同?

因为 [通常] 不是决定查询结构的提供者(即订购子查询)。提供者只是将查询结构转换为 DBMS 的语法。因此,在您的情况下,问题可能出在 DBMS 优化器上。

在与您类似的问题中,我使用了一种基于将查询映射到实体的不同方法,即使用ObjectContext.ExecuteStoreQuery

【讨论】:

  • 我相信 .ExecuteStoreQuery 只是为了运行我编写的普通 SQL 查询。我们可能不得不朝着那个方向前进,问题是这些查询是动态构建的,因此,目前摆脱 LINQ 将是一项艰巨的任务。
  • 使用 ExecuteStoreQuery,您可以运行您需要(由您)为商店优化的查询。通常很少有查询需要针对 DBMS 进行优化(否则您需要更改 ORM,即使用 Dapper)。大规模更新插入和删除是最糟糕的(SQL Server 中的解决方案是使用批量复制,但对于 MySQL,您不能使用相同的库)。关于 SQL 你看到查询是否以同样的方式嵌套?
  • 是的,dapper 已经在讨论中,不利的一面是,我们需要大量重写功能以摆脱 EF。在 SQL Server 中,它不会嵌套。我在问题中发布的第二个查询是由 MSSQL 生成的。因此,让我们知道 EF 的 MySQL 连接器是罪魁祸首。
【解决方案2】:

事实证明,为了使用 MySQL 驱动程序解决此问题,必须一次性编写整个 lambda。 ONE Where(..) 谓词中的含义。这样,驱动程序就知道这是一个结果集。现在,如果您构建一个初始 IQueryable,然后继续向其附加 Where 子句以访问子表,它将认为存在多个结果集,因此将您的整个查询包装到一个子选择中以便对其进行排序和限制。

【讨论】:

    猜你喜欢
    • 2014-07-17
    • 1970-01-01
    • 1970-01-01
    • 2011-09-17
    • 2011-11-04
    • 1970-01-01
    • 2014-06-05
    • 1970-01-01
    • 2017-07-19
    相关资源
    最近更新 更多