#c# #sql #.net #collections #ado.net
#c# #sql #.net #Коллекции #ado.net
Вопрос:
У меня есть приложение на C #, в котором у меня возникает эта проблема: когда я запускаю этот фрагмент:
Первый способ
public void GetList(List<string> liste, List<int> outliste)
{
foreach( string s in liste){
outliste.Add(SqlFunction(s));
}
}
public int SqlFunction(string str)
{
string query = "select id from user where name=" str;
...................
// return the id
}
время выполнения составляет 51 сек.
Второй способ
public void SqlSecondWayFunction(List<string> liste, List<int> outliste)
{
string query ="select id from user where (";
foreach(string str in liste){
query = "name=" str "or ";
}
query = " 1=0 )";
...................
// fill outliste by the result of the query
}
Время выполнения составляет 1 м: 19 секунд!!!!!!!!! (количество liste
составляет около 11000).
Итак, мне нужно знать, почему первый способ быстрее?
Комментарии:
1. Вы знаете, что оба они открыты для внедрения SQL?
2. @user2711965 Спасибо, я это знаю, это всего лишь краткий пример
3. второй пример объединяет все блоки where и связывает их с.
OR
затем в конце вы добавляете an1=1
к оператору where, который такжеOR
связан. это означает, что он возвращает все строки, потому1=1
что всегда имеет значение true4. Я бы предпочел знать, почему разница настолько мала. Выполнение запроса с условием, содержащим тысячи операторов, должно занять намного больше времени…
5. Я подозреваю, что основной проблемой во втором методе является построение вашего запроса с использованием
query =
, в каждом цикле это создает новую строку в куче, а не просто добавляет к существующей строке. Вместо этого используйте StringBuilder . Сказав, что есть гораздо лучшие и более безопасные способы передачи списка в качестве параметра. Какую СУБД вы используете?
Ответ №1:
Первый быстрее, потому что он выполняет меньше задач. И это только выбор подмножества записей.
Второй медленнее, потому что вы объединяете тонны строк (что довольно медленно), а также выполняете тысячи ненужных сравнений в базе данных для каждой записи, только для того, чтобы все равно возвращать каждую запись.
По сути, вы просите базу данных сравнить name
столбец с 11 000 строками для каждой записи в таблице. Если таблица содержит, например, 100 000 записей, то вы выполняете 1 100 000 000 сравнений строк. И затем вы все равно возвращаете все записи, потому что одним из ваших условий является «1 = 1», что всегда верно.
Комментарии:
1. Спасибо, я изменил
1=1
(см. Мое редактирование) и использую string builder вместо string, на этот раз я получил 54 секунды2. @Lamloumi: теперь он возвращает меньше записей. Но он по-прежнему выполняет множество конкатенаций строк и сравнений строк. Что именно вы спрашиваете?
3. я спрашиваю, почему первый способ остается самым быстрым, даже если я подключаюсь / закрываю соединение при каждом вызове метода, вторым способом я фильтрую список только один раз, чтобы получить результат, и он находится на стороне СУБД
4. ADO.NET использует пул соединений , поэтому, даже если вы открываете / закрываете соединение в своем коде, есть вероятность, что ADO.NET способен повторно использовать одно и то же соединение снова и снова, так что на самом деле вы не получаете затрат на открытие и закрытие соединения 11 000 раз.
Ответ №2:
Несколько вещей:
- Добавление длинного списка OR в ваше предложение WHERE обычно не очень эффективно из-за времени, которое требуется для первого анализа очень длинной строки запроса, и, кроме того, внутренней работы, выполняемой ядром базы данных для длинных списков OR
- Последняя добавляемая вами часть («ИЛИ 1 = 1») делает предложение WHERE ВСЕГДА ПРОВЕРЯЕМЫМ TRUE! Это то же самое, что вообще не иметь предложения WHERE
- Любое из вышеперечисленных действий может привести к тому, что ваш первый пример множества простых запросов займет меньше времени, чем один неэффективный большой запрос
- Вы должны рассмотреть одно из следующих действий для вашего варианта использования: 1) получите список идентификаторов, загрузив DataTable с помощью «select id from user», вызывая DataTable.Метод AsEnumerable(), а затем присоединение к вашему другому списку с помощью запроса LINQ, 2) сначала загрузка списка в таблицу базы данных, а затем использование SQL для присоединения пользовательской таблицы к вашему списку или 3) загрузка списка в список строк, разделенных запятыми, и использование SQL в операторе INвместо вашей конструкции name=x ИЛИ name=y ИЛИ name=z.
Ответ №3:
Используйте табличные параметры, если вы хотите передать список в SQL Server 2008. В вашей базе данных сначала вам нужно будет создать новый ТИП:
CREATE TYPE dbo.ListOfInt AS TABLE(Value INT);
Затем вы можете использовать это в качестве параметра, что-то вроде:
var table = new DataTable();
table.Columns.Add("Value", typeof(int));
for (int i = 0; i < liste.Count; i )
{
var row = table.NewRow();
row[0] = liste[i];
table.Rows.Add(row);
}
string sql = "SELECT ID FROM [User] WHERE ID IN (SELECT Value FROM @Liste)";
using (var connection = new SqlConnection("Your connection String"))
using (var command = new SqlCommand(sql, connection))
{
connection.Open();
var tvp = new SqlParameter("@Liste", SqlDbType.Structured).TypeName = "ListOfInt";
tvp.Value = table;
command.Parameters.Add(tvp);
using (var reader = command.ExecuteReader())
while (reader.Read())
{
outliste.Add(reader.GetInt("ID"));
}
}