Подтвердить что ты не робот

Операция Hibernate SQL In делает использование процессора до 100%

В моем приложении java я использую SQL server and Hibernate3 with EJB. Когда я попытался выполнить запрос select with In clause, использование ЦП сервера БД достигает 100%. Но когда я попытался запустить тот же запрос в SQL management studio, запрос выполняется без всплесков процессора. Сервер приложений и сервер БД - это две разные машины. Моя таблица имеет следующую схему,

CREATE TABLE student_table (
       Student_Id BIGINT NOT NULL IDENTITY
     , Class_Id BIGINT NOT NULL
     , Student_First_Name VARCHAR(100) NOT NULL
     , Student_Last_Name VARCHAR(100)
     , Roll_No VARCHAR(100) NOT NULL
     , PRIMARY KEY (Student_Id)
     , CONSTRAINT UK_StudentUnique_1 UNIQUE  (Class_Id, Roll_No)
);

В таблице содержится около 1000 тыс. записей. Мой запрос

select Student_Id from student_table where Roll_No in ('A101','A102','A103',.....'A250');

В разделе содержится 250 значений. Когда я попытался выполнить над запросом в студии управления SQL, результат будет получен в течение 1 секунды и без всплесков процессора. Но когда я попытался запустить один и тот же запрос через спящий режим, пики процессора достигают 100% в течение примерно 60 секунд, и результат получается около 60 секунд. Спящий запрос:

Criteria studentCriteria = session.createCriteria(StudentTO.class);
studentCriteria.add(Restrictions.in("rollNo", rollNoLists)); //rollNoLists is an Arraylist contains 250 Strings
studentCriteria.setProjection(Projections.projectionList().add(Projections.property("studentId")));
List<Long> studentIds = new ArrayList<Long>();
List<Long> results = (ArrayList<Long>) studentCriteria.list();
if (results != null && results.size() > 0) {
   studentIds.addAll(results);
}
return studentIds;

В чем проблема, почему это так. Если один и тот же запрос выполняется через студию управления, результат получается без всплесков, и результат получается в течение 1 секунды. Любое решение???

Edit1: Мой спящий сгенерированный запрос:

select this_.Student_Id as y0_ from student_table this_ where this_.Roll_No in (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?,?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

Edit2: Мой план выполнения Это было после индексации roll_no

CREATE INDEX i_student_roll_no ON student_table (Roll_No) 

My execution plan,

4b9b3361

Ответ 1

Запрос, запускаемый с консоли, легко кэшируется и поэтому ответ мгновенно. Если вы посмотрите на запрос, вы увидите, что все параметры встроены в запрос, поэтому планировщик запросов не может обнаружить там никаких изменений, и все исполнения всегда будут идти к одному и тому же плану и к тому же кэшированному результату.

Запрос, который вы запускаете с помощью Hibernate, даже если это был собственный запрос, использует PreparedStatement, а параметры привязываются во время выполнения запроса и to цитируйте одного из лучших авторов при индексировании:

Что это связано с параметрами привязки?

В кэша общих планов выполнения DB2, Oracle и SQL Server используется хэш-значение литеральной строки SQL в качестве ключа к кешу. Сохраненная копия планы не найдены, если SQL содержит литеральные значения, которые каждое исполнение.

Держатели мест (параметры привязки) унифицируют оператор так, чтобы строка SQL была идентичной при выполнении с разными значениями, увеличивая скорость попадания в кеш.

Чтобы решить эту проблему, вам нужно добавить индекс в столбцы (Roll_No, Student_Id), чтобы запрос стал проверкой только индекса.

SQL Server по умолчанию использует индексы кластера, которые ограничивают вас одним кластеризованным индексом для таблицы, поэтому вы можете захотеть вместо этого превратите эту таблицу в heap table и сосредоточьтесь только на индексах.

Ответ 2

Чтобы ответить на вопрос "почему он медленный через спящий режим", вам нужно увидеть фактический план выполнения, который используется сервером при запуске вашего спящего кода, а не план выполнения, который использует сервер при запуске запроса из SSMS. Снимок экрана с планом выполнения, который вы включили в вопрос, не похож на фактический план, который вы получаете при запуске своего спящего кода. Как только у вас есть этот план, вы можете сравнить его с планом, который вы получите от SSMS, и разница, скорее всего, объяснит, почему он медленный в одном случае и быстро в другом.

Существует очень хорошая статья Erland Sommarskog, в которой основное внимание уделяется так называемому "обнюхиванию параметров", что может стать причиной проблема здесь, но вряд ли. Что полезно для нас в этой статье, так это то, что он объясняет, как вы можете извлечь план выполнения для проверки из кеша.

Без этой информации вы можете только догадываться. Предполагается, что вы передаете свои параметры как nvarchar, но индексированное поле Roll_No равно varchar, поэтому индекс не используется. Сервер преобразует столбец varchar в nvarchar для сравнения, а это означает, что индекс не может использоваться = > он медленный, и преобразование может быть причиной высокой загрузки ЦП. http://sqlperformance.com/2013/04/t-sql-queries/implicit-conversion-costs https://www.sqlskills.com/blogs/jonathan/implicit-conversions-that-cause-index-scans/


Здесь нет ответа на ваш вопрос, но это возможное решение проблемы. Вместо того, чтобы передавать 250 отдельных параметров запросу для предложения IN, используйте table-valued parameter и передайте свои значения в виде таблицы. В запросе используйте JOIN вместо IN. Это становится особенно актуальным после ваших комментариев, что у вас будут параметры 100K (это означает, что вы хотите запустить запрос 400 раз). На самом деле, 100K слишком много даже для параметра таблицы, поэтому я бы подумал о наличии постоянной или временной вспомогательной таблицы, которая будет содержать эти Roll_No с правильным индексом. Основной запрос: JOIN. Что-то вроде этого:

CREATE TABLE RollNumbers (
     Roll_No VARCHAR(100) NOT NULL
     ,PRIMARY KEY (Roll_No)
);

Убедитесь, что в таблице RollNumbers есть индекс в Roll_No. Убедитесь, что в таблице student_table есть индекс в Roll_No. Сначала INSERT 100K значений в RollNumbers, а затем используйте их в основном запросе:

SELECT Student_Id 
FROM
    student_table
    INNER JOIN RollNumbers ON RollNumbers.Roll_No = student_table.Roll_No

В зависимости от общей системы таблица RollNumbers может быть постоянной таблицей, временной таблицей или табличной переменной.

Ответ 3

Проверьте тип данных на уровне гибернации всех полей, используемых в запросе, и убедитесь, что он соответствует определению вашей таблицы. В таких системах, как hibernate, используются типы поддерживаемых Unicode типов (например, nvarchar). Попробуйте изменить тип данных на любой стороне.

В качестве альтернативы вы можете добавить параметр, называемый sendStringParametersAsUnicode, в строку подключения. Это заставит спящий режим использовать varchar вместо nvarchar.

Просто дайте ему попробовать и сообщите нам!

Ответ 4

Вероятно, вы думаете, что, поскольку ваш медленный запрос занимает 60 секунд, ваш "быстрый" запрос занимает 1 секунду, на самом деле быстро. Это не тот случай. Эта разница в скорости выполнения не позволяет вам понять реальную проблему.

Дополнительная проблема (возможно, не актуальная проблема)

Самый простой тип запроса, который вы используете, должен возвращать результаты менее чем за миллисекунду, если у вас есть индекс на Roll_No, независимо от того, используете ли вы переменные связывания или встроенные значения.

Я просто предполагаю, что у вас нет индексов, кроме тех, которые генерируются из ограничений в вашей таблице. Таким образом, вы должны добавить простой индекс на Roll_No:

CREATE INDEX i_student_roll_no ON student_table (Roll_No);

Или вы можете добавить дополнительный столбец к указанному выше индексу, чтобы сделать его "охватывающим индексом" для этого запроса (как объяснил Влад)

CREATE INDEX i_student_roll_no2 ON student_table (Roll_No, Student_Id);

Это сделает этот конкретный запрос еще быстрее, так как план выполнения не понадобится снова ударять по диску, чтобы извлечь Student_Id из таблицы. Информация уже будет содержаться в индексе. Однако используйте индексы покрытия, однако, поскольку они:

  • Используйте больше места, особенно для таблицы среднего размера, такой как ваш
  • Работайте только до тех пор, пока ваши запросы ограничены точно столбцами, которые действительно покрыты, что вряд ли останется таким образом в вашем случае.

Как распознать это с помощью SQL Server Management Studio?

На самом деле очень хорошая функция в SQL Server Management Studio. Когда вы включаете планы выполнения (что вам нужно), вы получите дополнительную информацию о вашем запросе:

SQL Server Management Studio execution plans

Щелкните правой кнопкой мыши эту информацию и выберите "Отсутствующие данные индекса...", чтобы получить информацию, подобную этой:

/*
Missing Index Details from SQLQuery1.sql - 
  LUKAS-ENVY\SQLEXPRESS.test (LUKAS-ENVY\Lukas (52))
The Query Processor estimates that implementing the 
  following index could improve the query cost by 87.5035%.
*/

/*
USE [test]
GO
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[student_table] ([Roll_No])
INCLUDE ([Student_Id])
GO
*/

Почему разница между SQL Server Mgmt Studio и Hibernate?

Ваш исходный вопрос еще не дан ответ. Возможно, дополнительный индекс исправляет его, но, возможно, вы не предоставили всю информацию. Вы могли бы:

  • Признаки с переменными pinding
  • Задачи N + 1 в Hibernate (с таким количеством строк)

Ответ 5

По странному совпадению, я столкнулся с этой проблемой только на этой неделе, и это не проблема Hibernate N + 1, о которой здесь упоминают многие люди. Я использую Amazon Redshift с драйвером Postgres Java.

По причинам, в которые я не буду входить, я использовал инструкцию с параметром WHERE IN() длиной 7 000, которая при запуске с базой данных приводит к выполнению 10-секундного запроса (это сложный запрос на большой таблице, и мы еще не установили индексирование - все еще в разработке, мы еще не начали настройку). При запуске через Hibernate выполняется 120-секундное время выполнения запроса.

Я обнаружил, что если вы получите фактическую строку SQL из Hibernate, замените (?,?...?) на String из фактических значений и запустите это (все еще через Hibernate), внезапно все возвращается в 10 секунд.

Копаясь во внутренней среде Hibernate, оказывается, что они выполняют нетривиальное количество обработки для каждого параметра, что приводит к первоначальному всплеску процессора и раздутому времени выполнения для операторов с большим количеством параметров.

Кроме того, как только запрос, в конечном счете, отправляется в базу данных, процессор сервера базы данных достигает 100% в течение всего времени использования параметров, но не без параметров. Я не проверял точные тайминги, сколько всей этой обработки происходит на какой стороне забора, но похоже, что использование многих параметров не является жизнеспособным ни на стороне Hibernate, ни на стороне базы данных.

Решение? Используйте меньше параметров. Или найдите базу данных, которая поддерживает большие наборы параметров, оставаясь при этом исполняемой.

Вероятно, мы перейдем из Hibernate в jOOQ, поскольку jOOQ позволяет определять ваши собственные пользовательские фрагменты SQL, которые работают с официальным DSL. Затем мы создадим предложение IN() вручную без параметров. Мы можем это сделать, поскольку наши переменные IN являются внутренними идентификаторами, поэтому SQL-инъекция не является возможностью, но если SQL-инъекция является возможностью, убедитесь, что вы дезинфицируете свои входы.

Ответ 6

Похоже, вы получили не все записи при выполнении запроса не из спящего режима, а в свой код все записи из выполнения запроса, помещенные в коллекцию.

Ответ 8

Я просто указываю, что эта часть LBushkin отвечает за вас от этого сообщения

Во-вторых, при использовании IN или OR с переменным числом аргументы, вы заставляете базу данных повторно анализировать запрос и перестраивать план выполнения при каждом изменении аргументов. Здание план выполнения запроса может быть дорогостоящим шагом. Наиболее базы данных кэшируют планы выполнения запросов, которые они запускают, используя EXACT текст запроса в качестве ключа. Если вы выполняете аналогичный запрос, но с разные значения аргументов в предикате - вы, скорее всего, заставляют базу данных тратить значительное количество времени на разбор и планы строительства. Вот почему переменные связывания сильно рекомендуется как способ обеспечения оптимальной производительности запросов.

Итак, вы можете попробовать привязать переменные, чтобы каждый раз запускать план выполнения

Использование переменных привязки (параметризованные запросы в SQL Server)