代码之家  ›  专栏  ›  技术社区  ›  Cine

SQL大量ID选择-如何使其更快?

  •  2
  • Cine  · 技术社区  · 15 年前

    我有一个包含大量ID的数组,我想从DB中选择。

    通常的做法是这样做 select blabla from xxx where yyy IN (ids) OPTION (RECOMPILE) 选项重新编译 是必需的,因为SQL server不够智能,无法看到将此查询放入其查询缓存是对内存的巨大浪费)

    然而,当ID数量很大时,SQL Server在这种类型的查询中非常糟糕,它使用的解析器太慢了。 让我举一个例子:

    SELECT * FROM table WHERE id IN (288525, 288528, 288529,<about 5000 ids>, 403043, 403044) OPTION (RECOMPILE)
    

    msec(在我的示例中,它返回appx 200行)

    与:

    SELECT * FROM table WHERE id BETWEEN 288525 AND 403044 OPTION (RECOMPILE)
    

    执行时间: ~80 msec(在我的示例中,它返回appx 50000行)

      private const int MAX_NUMBER_OF_EXTRA_OBJECTS_TO_FETCH = 5;
      public static string MassIdSelectionStringBuilder(
           List<int> keys, ref int startindex, string colname)
      {
         const int maxlength = 63000;
         if (keys.Count - startindex == 1)
         {
            string idstring = String.Format("{0} = {1}", colname, keys[startindex]);
            startindex++;
            return idstring;
         }
         StringBuilder sb = new StringBuilder(maxlength + 1000);
         List<int> individualkeys = new List<int>(256);
         int min = keys[startindex++];
         int max = min;
         sb.Append("(");
         const string betweenAnd = "{0} BETWEEN {1} AND {2}\n";
         for (; startindex < keys.Count && sb.Length + individualkeys.Count * 8 < maxlength; startindex++)
         {
            int key = keys[startindex];
            if (key > max+MAX_NUMBER_OF_EXTRA_OBJECTS_TO_FETCH)
            {
               if (min == max)
                  individualkeys.Add(min);
               else
               {
                  if(sb.Length > 2)
                     sb.Append(" OR ");
                  sb.AppendFormat(betweenAnd, colname, min, max);
               }
               min = max = key;
            }
            else
            {
               max = key;
            }
         }
         if (min == max)
            individualkeys.Add(min);
         else
         {
            if (sb.Length > 2)
               sb.Append(" OR ");
            sb.AppendFormat(betweenAnd, colname, min, max);
         }
         if (individualkeys.Count > 0)
         {
            if (sb.Length > 2)
               sb.Append(" OR ");
            string[] individualkeysstr = new string[individualkeys.Count];
            for (int i = 0; i < individualkeys.Count; i++)
               individualkeysstr[i] = individualkeys[i].ToString();
            sb.AppendFormat("{0} IN ({1})", colname,  String.Join(",",individualkeysstr));
         }
         sb.Append(")");
         return sb.ToString();
      }
    

    然后使用如下方式:

     List<int> keys; //Sort and make unique
     ...
     for (int i = 0; i < keys.Count;)
     {
        string idstring = MassIdSelectionStringBuilder(keys, ref i, "id");
        string sqlstring = string.Format("SELECT * FROM table WHERE {0} OPTION (RECOMPILE)", idstring);
    

    然而,我的问题是。。。 有人知道更好/更快/更聪明的方法吗?

    7 回复  |  直到 15 年前
        1
  •  2
  •   A-K    15 年前

    根据我的经验,最快的方法是将二进制格式的数字打包成一个图像。我发送了多达10万个ID,效果很好:

    Mimicking a table variable parameter with an image

    然而这是很久以前的事了。Erland Sommarskog的以下文章是最新的:

    Arrays and Lists in SQL Server

        2
  •  1
  •   Neil    15 年前

    如果ID列表位于另一个索引表中,则使用简单的 INNER JOIN

    如果不可能,那么尝试创建一个表变量,如下所示

    DECLARE @tTable TABLE
    (
       @Id int
    )
    

    首先将ID存储在表变量中,然后 内连接 对于你的表xxx,我用这种方法取得的成功有限,但值得一试

        3
  •  1
  •   MSalters    15 年前

    (key > max+MAX_NUMBER_OF_EXTRA_OBJECTS_TO_FETCH) 作为确定是否执行范围提取而不是单个提取的检查。看来这不是最好的办法。

    让我们考虑4个ID序列{ 2, 7 },{2,8},{1,2,7},和{1,2,8}。 它们转化为

    ID BETWEEN 2 AND 7
    ID ID in (2, 8)
    ID BETWEEN 1 AND 7 
    ID BETWEEN 1 AND 2 OR ID in (8)
    

    获取和过滤ID 3-6的决定现在只取决于2和7/8之间的差异。但是,它不考虑2是否已经是范围或单个ID的一部分。

    我认为正确的标准是你保存了多少个人ID。将两个人转换为一个范围将带来 2 * Cost(Individual) - Cost(range) 而扩大范围的净收益为 Cost(individual) - Cost(range extension) .

        4
  •  0
  •   zapping    15 年前

    添加重新编译不是一个好主意。预编译意味着sql不会保存查询结果,但会保存执行计划。从而使查询速度更快。如果添加“重新编译”,则它将始终具有编译查询的开销。尝试创建一个存储过程,保存查询并从那里调用它。因为存储过程总是预编译的。

        5
  •  0
  •   RameshVel    15 年前

    另一个与Neils相似的肮脏想法,

    • 有一个索引视图,它根据您的业务条件单独保存ID
        6
  •  0
  •   RobC    15 年前

    有效的方法是:

    1. 创建一个临时表来保存ID
    2. 使用包含所有逗号分隔ID的字符串参数调用SQL存储过程
    3. 内部联接临时表或在in(从@temporary中选择ID)子查询中使用它

    这些步骤中的每一步都非常快,因为只传递一个字符串,循环期间不进行编译,并且除了实际的id值之外,不创建任何子字符串。

    只要将大字符串作为参数传递,就不会执行任何重新编译。

    请注意,在循环中,必须在两个单独的值中跟踪前一个逗号和当前逗号

        7
  •  0
  •   onupdatecascade    15 年前

    即兴——合并派生表是否有助于提高性能?我并没有做好充分测试的准备,只是想知道这是否会优化到使用between,然后过滤掉不需要的行:

    Select * from 
    ( SELECT *
      FROM dbo.table 
      WHERE ID between <lowerbound> and <upperbound>) as range
    where ID in ( 
        1206,
        1207,
        1208,
        1209,
        1210,
        1211,
        1212,
        1213,
        1214,
        1215,
        1216,
        1217,
        1218,
        1219,
        1220,
        1221,
        1222,
        1223,
        1224,
        1225,
        1226,
        1227,
        1228,
        <...>,
        1230,
        1231
    )