15

我有一个 SQL 表,我想按 ID 选择多行。例如,我想从我的表中获取 ID 为 1、5 和 9 的行。

我一直在使用类似于以下的 WHERE IN 语句来执行此操作:

SELECT [Id]
FROM [MyTable]
WHERE [Id] IN (1,5,9)

但是,对于“IN”子句中的大量项目,这非常慢

以下是使用 where in 从具有 1,000,000 行的表中选择行的一些性能数据

Querying for 1 random keys (where in) took 0ms
Querying for 1000 random keys (where in) took 46ms
Querying for 2000 random keys (where in) took 94ms
Querying for 3000 random keys (where in) took 249ms
Querying for 4000 random keys (where in) took 316ms
Querying for 5000 random keys (where in) took 391ms
Querying for 6000 random keys (where in) took 466ms
Querying for 7000 random keys (where in) took 552ms
Querying for 8000 random keys (where in) took 644ms
Querying for 9000 random keys (where in) took 743ms
Querying for 10000 random keys (where in) took 853ms

有没有比使用 WHERE IN 更快的方法来执行此操作。

我们不能进行连接,因为这是在断开连接的系统之间。

我听说在 MYSQL 中加入数据的内存临时表可能会更快,但根据我的研究,MSSQL 没有内存表选项,即使这样,插入时也不会出现完全相同的索引扫描作为 WHERE IN 的临时表?

编辑:

该表将 ID 作为 PK,因此具有默认的 PK 索引,cf

CREATE TABLE [dbo].[Entities](
    [Id] [int] IDENTITY(1,1) NOT NULL,
 CONSTRAINT [PK_dbo.Entities] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

执行计划

在此处输入图像描述

这是生成这些性能结果的控制台应用程序的 GIST https://gist.github.com/lukemcgregor/5914774

编辑 2 我创建了一个函数,它从逗号分隔的字符串创建一个临时表,然后加入该表。它更快,但我认为主要是因为解析查询的问题

Querying for 1 random keys took 1ms
Querying for 1000 random keys took 34ms
Querying for 2000 random keys took 69ms
Querying for 3000 random keys took 111ms
Querying for 4000 random keys took 143ms
Querying for 5000 random keys took 182ms
Querying for 6000 random keys took 224ms
Querying for 7000 random keys took 271ms
Querying for 8000 random keys took 315ms
Querying for 9000 random keys took 361ms
Querying for 10000 random keys took 411ms
4

3 回答 3

10

好的,所以我通过定义一个表类型,然后将该类型直接传递到查询中并加入它,让它变得非常快。

在 SQL 中

CREATE TYPE [dbo].[IntTable] AS TABLE(
    [value] [int] NULL
)

在代码中

DataTable dataTable = new DataTable("mythang");
dataTable.Columns.Add("value", typeof(Int32));

toSelect.ToList().ForEach(selectItem => dataTable.Rows.Add(selectItem));

using (SqlCommand command = new SqlCommand(
    @"SELECT * 
    FROM [dbo].[Entities] e 
    INNER JOIN @ids on e.id = value", con))
{
    var parameter = command.Parameters.AddWithValue("@ids", dataTable);
    parameter.SqlDbType = System.Data.SqlDbType.Structured;
    parameter.TypeName = "IntTable";

    using (SqlDataReader reader = command.ExecuteReader())
    {
        while (reader.Read())
        {
            results.Add(reader.GetInt32(0));
        }
    }
}

这会产生以下结果

Querying for 1 random keys (passed in table value) took 2ms
Querying for 1000 random keys (passed in table value) took 3ms
Querying for 2000 random keys (passed in table value) took 4ms
Querying for 3000 random keys (passed in table value) took 6ms
Querying for 4000 random keys (passed in table value) took 8ms
Querying for 5000 random keys (passed in table value) took 9ms
Querying for 6000 random keys (passed in table value) took 11ms
Querying for 7000 random keys (passed in table value) took 13ms
Querying for 8000 random keys (passed in table value) took 17ms
Querying for 9000 random keys (passed in table value) took 16ms
Querying for 10000 random keys (passed in table value) took 18ms
于 2013-07-03T04:02:05.163 回答
3

我想如果您将表与由主键索引的内存表连接起来,例如:

declare @tbl table (ids int primary key)

您可以用您需要的 id 填充此表,并执行优化的内部连接。

问题可能是填充它所需的时间。我想您可以为此使用链接服务器,也可以使用 BCP 实用程序来填充临时表,然后将其删除。

于 2013-07-03T00:33:02.750 回答
2

首先,我认为声称您的数据暗示O(n log(n)). (顺便说一句,您进行了性能测试真是太好了。)这是每个值的时间:

1000    0.046
2000    0.047
3000    0.083
4000    0.079
5000    0.078
6000    0.078
7000    0.079
8000    0.081
9000    0.083
10000   0.085

虽然随着时间的推移略有增加,但从 2000 到 3000 的跃迁要突出得多。如果这是可重现的,那么我的问题是为什么会出现这种不连续性。

对我来说,这是更多的建议O(n)and O(n log(n))。但是,理论值的经验估计很难近似。因此,确切的限制并不那么重要。

我希望性能是O(n)n实际值在哪里,而不是在某些估计中的位长)。我的理解是,它的in行为就像一组巨大的ors。大多数记录未通过测试,因此他们必须进行所有比较。因此O(n).

下一个问题是您是否在 id 字段上有索引。O(n log(n)) time (在这种情况下,您可以在log (n) for traversing the index andn` 中获取一组匹配的 id,以便为每个值执行此操作)。这看起来更糟,但我们忽略了原始表格大小的因素。这应该是一个很大的胜利。

正如 Andre 所建议的,您可以加载一个表并连接到一个临时表。我会省略索引,因为您最好在更大的表上使用索引。这应该让你O(n log(n))- 对原始表的大小没有(显着)依赖。或者,您可以省略索引并知道原始表的大小在O(n * m)哪里。m我认为在临时表上构建的任何索引都可以让您恢复O(n log(n))性能(假设数据没有预先排序)。

将所有内容放在查询中都有一个类似的、未说明的问题——解析查询。随着字符串变长,这需要更长的时间。

简而言之,我赞扬你进行性能测量,而不是对算法复杂性得出结论。我不认为你的数据支持你的结论。此外,查询的处理比您建议的要复杂一些,并且您忽略了较大表的大小 - 这可能会产生主要影响。而且,我很好奇 2000 到 3000 行之间发生了什么。

于 2013-07-03T01:07:32.983 回答