我有一个包含大量ID的数组,我想从数据库中选择。
通常的做法是select blabla from xxx where yyy IN (ids) OPTION (RECOMPILE)
。
(需要选项重新编译,因为SQL服务器不够智能,无法看到将此查询放入其查询缓存中是一个巨大的内存浪费)
然而,当ID数量很高时,SQL Server在这种类型的查询中非常糟糕,它使用的解析器太慢了。 让我举个例子:
SELECT * FROM table WHERE id IN (288525, 288528, 288529,<about 5000 ids>, 403043, 403044) OPTION (RECOMPILE)
执行时间: ~1100 毫秒(在我的示例中返回appx 200行)
对战:
SELECT * FROM table WHERE id BETWEEN 288525 AND 403044 OPTION (RECOMPILE)
执行时间: ~80 毫秒(在我的示例中返回appx 50000行)
所以尽管我得到的数据还要多250倍,但执行速度要快14倍......
所以我构建了这个函数来获取我的id列表并构建一些能够在两者之间返回合理折衷的东西(这些东西不会返回250倍的数据,但仍然可以更快地解析查询)
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);
然而,我的问题是...... 有没有人知道更好/更快/更聪明的方法呢?
答案 0 :(得分:2)
根据我的经验,最快的方法是将二进制格式的数字打包成图像。我发送了多达100K的ID,效果很好:
Mimicking a table variable parameter with an image
还有一段时间以前。 Erland Sommarskog的以下文章是最新的:
答案 1 :(得分:1)
如果ID列表在另一个被索引的表中,那么使用简单的INNER JOIN
如果不可能,那么尝试创建一个像这样的TABLE变量
DECLARE @tTable TABLE
(
@Id int
)
首先将ids存储在表变量中,然后将INNER JOIN
存储到表xxx中,我使用此方法取得了有限的成功,但值得一试
答案 2 :(得分:1)
您正在使用(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)
的净收益。
答案 3 :(得分:0)
添加重新编译不是一个好主意。预编译意味着sql不保存您的查询结果,但它保存了执行计划。从而试图使查询更快。如果添加重新编译,那么它将始终具有编译查询的开销。尝试创建存储过程并保存查询并从那里调用它。由于存储过程始终是预编译的。
答案 4 :(得分:0)
与Neils类似的另一个肮脏的想法,
答案 5 :(得分:0)
这样做的有效方法是:
这些步骤中的每一步都非常快,因为传递了一个字符串,在循环期间没有进行编译,并且除了实际的id值之外没有创建子字符串。
只要将大字符串作为参数传递,就不会重新编译。
请注意,在循环中,您必须在两个单独的值中跟踪先前和当前逗号
答案 6 :(得分:0)
在这里关闭袖口 - 是否合并了派生表帮助表现?我没有设置完全测试,只是想知道这是否会优化使用之间,然后过滤掉不需要的行:
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
)