【问题标题】:How to efficiently SELECT rows from database table based on selected set of values如何根据选定的一组值有效地从数据库表中选择行
【发布时间】:2010-04-24 05:03:05
【问题描述】:

我有一个 100 万行的事务表。该表有一个字段名称“代码”来保存客户的 ID。大约有 10,000 个不同的客户代码。

我有一个 GUI 界面允许用户从事务表中呈现报告。用户可以选择任意数量的客户进行渲染。

我首先使用 IN 运算符,它适用于少数客户:

SELECT * FROM TRANS_TABLE WHERE CODE IN ('...', '...', '...')

如果我选择几千名客户,我很快就会遇到问题。使用 IN 运算符有限制。

另一种方法是创建一个只有一个 CODE 字段的临时表,并使用 INSERT 语句将选定的客户代码注入到临时表中。然后我可以使用

SELECT A.* FROM TRANS_TABLE A INNER JOIN TEMP B ON (A.CODE=B.CODE)

这适用于大量选择。但是,临时表的创建、INSERT 注入和临时表的删除都会产生性能开销。

您知道处理这种情况的更好解决方案吗?

【问题讨论】:

  • 这是一个典型的问题。第一个问题是,用户会选择这么多客户吗?什么是常规用例?
  • 常规用例是选择 1 个客户。但总是有人要求选择任意客户。我希望找到一个银弹解决方案,可以满足所有要求,而无需做太多 if..then..else 来满足每个要求。

标签: sql performance implementation


【解决方案1】:

如果您使用 SQL Server 2008,最快的方法通常是使用Table-Valued Parameter (TVP):

CREATE TYPE CodeTable AS TABLE
(
    Code int NOT NULL PRIMARY KEY
)

DECLARE @Codes AS CodeTable
INSERT @Codes (Code) VALUES (1)
INSERT @Codes (Code) VALUES (2)
INSERT @Codes (Code) VALUES (3)
-- Snip codes

SELECT t.*
FROM @Codes c
INNER JOIN Trans_Table t
    ON t.Code = c.Code

使用 ADO.NET,您可以填充 TVP directly from your code,因此您不需要生成所有这些 INSERT 语句 - 只需传入 DataTable,ADO.NET 将处理其余的。所以你可以这样写一个存储过程:

CREATE PROCEDURE GetTransactions
    @Codes CodeTable READONLY
AS

SELECT t.*
FROM @Codes c
INNER JOIN Trans_Table t
    ON t.Code = c.Code

...只需将@Codes 值作为参数传入。

【讨论】:

    【解决方案2】:

    可以生成SQL如

    SELECT * FROM TRANS_TABLE WHERE CODE IN (?,?,?,?,?,?,?,?,?,?,?)
    

    并在循环中重复使用它,直到您加载了所需的所有 ID。优点是,如果您只需要几个 ID,您的数据库就不需要解析所有这些子句。如果许多 ID 很少见,那么性能影响可能并不重要。如果您不担心 SQL 解析缓存,则可以将 in 子句的大小限制为 DB 的实际限制,这样有时不需要循环,有时则需要。

    【讨论】:

      【解决方案3】:

      由于您必须以某种方式传递 ID,因此 IN 应该是最快的方式。

      MSDN 提到:

      在 IN 子句中包含大量值(数千个)会消耗资源并返回错误 8623 或 8632。要解决此问题,请将 IN 列表中的项目存储在表中。

      如果您仍然可以使用 IN 并且查询速度很慢,您可以尝试调整您的索引,例如为您的查询使用一些覆盖索引。由于需要随机磁盘 I/O,因此通过聚集索引查找随机值可能会很慢。覆盖索引可以减少这个问题。

      如果你真的通过了 IN 的限制并且你创建了一个临时表,我不认为创建表是一个大问题,只要你一次插入值(当然不是数千个查询) .选择开销最小的方法,比如这里提到的方法之一:

      http://blog.sqlauthority.com/2008/07/02/sql-server-2008-insert-multiple-records-using-one-insert-statement-use-of-row-constructor/

      当然,如果您的 ID 中有一些静态模式,您可以通过它来选择(例如在 SP 或 UDF 中)。如果您从数据库本身中获取这数千个 ID,而不是来回传递它们,您可以只存储它们或使用子查询...

      【讨论】:

      • 我的应用程序使用不同类型的数据库范围从 Firebird、MSSQL 和 MySQL。这些数据库可能有不同的 IN 列表限制。
      【解决方案4】:

      也许您可以将客户代码传递给以逗号分隔的存储过程,并使用此处提到的拆分 sql 函数:http://www.devx.com/tips/Tip/20009

      然后声明一个标量表,在其中插入拆分的值并使用 IN 子句。

      CREATE PROCEDURE prc_dosomething (
          @CustomerCodes varchar(MAX)
      )
      AS
      
      DECLARE @customercodetable table(code varchar(10)) -- or whatever length you require.
      SET @customercodetable = UTILfn_Split(@CustomerCodes) -- see the article above for the split function.
      
      -- do some magic stuff here :).
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-12-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多