Видалення оператора в SQL відбувається дуже повільно


77

У мене є такі заяви, які вичерпують час:

DELETE FROM [table] WHERE [COL] IN ( '1', '2', '6', '12', '24', '7', '3', '5')

Я намагався робити по одному за таким чином:

DELETE FROM [table] WHERE [COL] IN ( '1' )

і поки що це 22 хвилини і все ще йде.

Таблиця містить 260 000 рядків і складається з чотирьох стовпців.

Хтось має ідеї, чому це буде так повільно і як це пришвидшити? У мене є не унікальний, некластеризований індекс на [COL], на якому я роблю WHERE. Я використовую SQL Server 2008 R2

оновлення: у мене немає тригерів на столі.


1
що станеться, якщо ви це зробите where [col] = '1'?
Алекс Гітельман,

3
ви перевіряли план виконання запиту, щоб побачити, чи він щось показує?
Тарин

3
Оператор DELETE, як правило, повільний через журнал. TRUNCATE швидше. але ви не можете використовувати TRUNCATE у цій ситуації. Я не маю жодних додаткових
понять


Повинен бути тригер або щось таке, що робить глухий кут.
Сергій Калініченко

Відповіді:


89

Речі, які можуть спричинити повільне видалення:

  • видалення великої кількості записів
  • багато індексів
  • відсутні індекси зовнішніх ключів у дочірніх таблицях. (дякую @CesarAlvaradoDiaz за згадку про це в коментарях)
  • тупикові ситуації та блокування
  • тригери
  • каскадне видалення (ці десять батьківських записів, які ви видаляєте, можуть означати видалення мільйонів дочірніх записів)
  • Журнал транзакцій потребує зростання
  • Багато іноземних ключів для перевірки

Тож ваш вибір полягає в тому, щоб з’ясувати, що блокує, і виправити це, або запустити видалення в неробочі години, коли вони не будуть заважати нормальному виробничому навантаженню. Ви можете запускати видалення пакетами (корисно, якщо у вас є тригери, каскадне видалення або велика кількість записів). Ви можете скидати та відтворювати індекси (найкраще, якщо це можна зробити і в неробочі години).


28
У мене був випадок, коли я намагався видалити з таблиці, що містить ~ 1 мільйон рядків, але це зайняло назавжди. Запити рядків за допомогою select *були швидкими, але видалення було шалено повільним. Потім я зрозумів, що до столу є зовнішній ключ з іншого, що має 2 мільярди (!) Рядків. Звичайно, колонка ФК не індексувалася. Рішення: скинуто FK, видалено рядки, відтворено FK. Відтворення ФК все ще зайняло деякий час, але це було набагато швидше.
Даніель Дінніс

5
Важливо: якщо у вас є зовнішні ключі, вони у ваших таблицях повинні бути проіндексовані
Сезар Альварадо Діас,

Чи є гарною практикою вимикати тригер під час видалення величезного списку записів із таблиці?
aagjalpankaj

1
@Aviator, лише якщо ваш скрипт видалення обробляє те, що колись оброблялося тригером. В іншому випадку ви, ймовірно, спричините величезну проблему цілісності даних.
HLGEM

64
  1. Вимкніть CONSTRAINT

    ALTER TABLE [TableName] NOCHECK CONSTRAINT ALL;

  2. Вимкнути індекс

    ALTER INDEX ALL ON [TableName] DISABLE;

  3. Індекс відновлення

    ALTER INDEX ALL ON [TableName] REBUILD;

  4. Увімкнути CONSTRAINT

    ALTER TABLE [TableName] CHECK CONSTRAINT ALL;

  5. Знову видаліть


15
Це спрацювало, але пояснення було б непоганим.
cdonner

2
Один недолік полягає в тому, що після того, як alter index all on mytable disableбільшість запитів перестають працювати. Msgstr "Процесор запитів не може створити план". Здається, вони знову працюють після того, як індекси були відновлені та обмеження ввімкнені.
Ed Avis,

При вимкненні індексів у таблиці також будуть вимкнені всі зовнішні клавіші, що вказують на таблицю. Це може бути поясненням прискорення. Згодом ви можете select referrer, fk from foreign_keys where is_disabled = 1або перевірити попереджувальні повідомлення, які з’являлися під час відключення індексів. Тоді для кожного постраждалого дитини таблиці alter table mychild with check check constraint all. Це може знайти деякі дочірні рядки, які тепер потрібно видалити!
Ed Avis

Ви також можете отримати FK, позначені як "не довіряють", тому select referrer, fk from foreign_keys where is_not_trusted = 1виправте їх також.
Ed Avis

2
ПОПЕРЕДЖЕННЯ: Перебудова індексів у великій таблиці (2 мільйони +) займає БАГАТО часу (годин ...). У моєму випадку було краще просто зробити так, як у відповіді @ Andomar.
マ ル ち ゃ ん だ よ

21

Видалення великої кількості рядків може бути дуже повільним. Спробуйте видалити кілька за раз, наприклад:

delete top (10) YourTable where col in ('1','2','3','4')
while @@rowcount > 0
    begin
    delete top (10) YourTable where col in ('1','2','3','4')
    end

4

Профілактична дія

Перевірте за допомогою SQL Profilerосновної причини цієї проблеми. Можливо, Triggersпричина затримки виконання. Це може бути що завгодно. Не забудьте вибрати Database Nameта Object Nameпід час запуску, Traceщоб виключити сканування непотрібних запитів ...

Фільтрування імен баз даних

Таблиця / Збережена процедура / Фільтрування імен тригера

Коригувальні дії

Як ви вже сказали, ваша таблиця містить 260 000 записів ... і IN Predicateмістить шість значень. Зараз кожен запис здійснюється пошук 260 000 разів для кожного значення в IN Predicate. Натомість це має бути Внутрішнє приєднання, як показано нижче ...

Delete K From YourTable1 K
Inner Join YourTable2 T on T.id = K.id

Вставте IN Predicateзначення в Temporary TableабоLocal Variable


3

Якщо таблиця, з якої ви видаляєте, має тригери ДО / ПІСЛЯ ВИДАЛЕННЯ, щось там може спричинити затримку.

Крім того, якщо у вас є зовнішні ключі, що посилаються на цю таблицю, можуть відбутися додаткові ОНОВЛЕННЯ або ВИДАЛЕННЯ.


3

У моєму випадку статистика бази даних пошкодилася. Заява

delete from tablename where col1 = 'v1' 

зайняло 30 секунд, хоча відповідних записів не було, але

delete from tablename where col1 = 'rubbish'

побіг миттєво

біг

update statistics tablename

вирішено проблему


2

Можливо, інші таблиці мають обмеження FK для вашої [таблиці]. Отже, БД потрібно перевірити ці таблиці, щоб зберегти цілісність посилань. Навіть якщо у вас є всі необхідні індекси, що відповідають цим ФК, перевірте їх кількість.

У мене була ситуація, коли NHibernate неправильно створив дубльовані FK- файли на тих самих стовпцях, але з різними іменами (що дозволено SQL Server). Це різко уповільнило роботу оператора DELETE.


1
Це повинен був бути коментар, а не відповідь. Трохи більше представників ви зможете залишати коментарі .
IKavanagh

1

Перевірте план виконання цього оператора видалення. Подивіться, чи використовується пошук за індексом. Також що таке тип даних col?

Якщо ви використовуєте неправильний тип даних, змініть оператор оновлення (наприклад, з '1' на 1 або N'1 ').

Якщо використовується сканування індексу, розгляньте можливість використання підказки ..


У моєму випадку відсутній індекс. Додавання індексу вирішило проблему.
Натрій

0

[COL] - це справді поле символів, яке містить цифри, або ви можете позбутися одинарних лапок навколо значень? @Alex має рацію, що IN повільніше ніж =, тому, якщо ви можете це зробити, вам буде краще:

DELETE FROM [table] WHERE [COL] = '1'

Але краще все-таки використовувати числа, а не рядки для пошуку рядків (sql любить числа):

 DELETE FROM [table] WHERE [COL] = 1

Може спробувати:

 DELETE FROM [table] WHERE CAST([COL] AS INT) = 1

У будь-якому випадку переконайтеся, що у вас є індекс у стовпці [COL], щоб пришвидшити сканування таблиці.


На жаль, стовпець містить літери та цифри, і я маю індекс = /.
Кайл

Я не впевнений, чи допоможе це, але одне, що ми робимо тут, - це помістити стовпець "IsActive" у всі наші важливі таблиці, що дозволяє нам оновлювати поле, а не видаляти рядки. Зручний для аудиту та менш безладний з точки зору відновлення індексів при видаленні. Звичайно, усі наступні подання / запити / прокси / функції повинні містити "WHERE ISACTIVE = 1".
Рассел Фокс,

2
Передача всіх значень у базі даних в int може насправді значно погіршити продуктивність. Це також може запобігти тому, що сервер sql може використовувати індекс. Отже, якщо ваша пропозиція має якийсь ефект, це, швидше за все, гірше.
Stefan Steinegger

0

Я прочитав цю статтю, вона була дуже корисною для усунення будь-яких незручностей

https://support.microsoft.com/en-us/kb/224453

це випадок очікування джерела КЛЮЧ: 16: 72057595075231744 (ab74b4daaf17)

-- First SQL Provider to find the SPID (Session ID)

-- Second Identify problem, check Status, Open_tran, Lastwaittype, waittype, and waittime
-- iMPORTANT Waitresource select * from sys.sysprocesses where spid = 57

select * from sys.databases where database_id=16

-- with Waitresource check this to obtain object id 
select * from sys.partitions where hobt_id=72057595075231744

select * from sys.objects where object_id=2105058535

0

Якщо ви видаляєте всі записи в таблиці, а не декілька вибраних, можливо, набагато швидше просто скинути та відтворити таблицю.


0

Перевіривши пакет SSIS (через те, що SQL Server виконує команди дуже повільно), який був встановлений у нашого клієнта приблизно за 5-4 роки до мого написання цього, я виявив, що були такі завдання: 1 ) вставити дані з XML-файлу в таблицю з назвою [Importbarcdes].

2) команда злиття в іншій цільовій таблиці, використовуючи як джерело згадану таблицю.

3) "видалити з [Імпорт-коди]", щоб очистити таблицю рядка, який був вставлений після прочитання файлу XML завданням пакета SSIS.

Після швидкої перевірки всіх операторів (SELECT, UPDATE, DELETE тощо) таблиці ImportBarcode, які мали лише 1 рядок, знадобилося приблизно 2 хвилини для виконання.

Розширені події показали дуже багато сповіщень про очікування PAGEIOLATCH_EX.

У таблиці відсутні індекси та не було зареєстровано тригерів.

Після ретельного вивчення властивостей таблиці, на вкладці «Зберігання» та в загальному розділі поле «Простір даних» показало більше 6 ГІГАБІТ місця, виділеного на сторінках.

Що трапилось:

Запит виконувався протягом великих порцій часу щодня протягом останніх 4 років, вставляючи та видаляючи дані в таблиці, залишаючи невикористані файли сторінок, не звільняючи їх.

Отже, це була основна причина подій очікування, які були зафіксовані розширеною сесією подій та повільно виконуваних команд на столі.

Запуск ALTER TABLE ImportBarcodes REBUILDвирішив проблему, звільнивши весь невикористаний простір. TRUNCATE TABLE ImportBarcodesзробив подібне, з тією лише різницею, що видалив усі файли сторінок та дані.


0

Давніша тема, але одна, як і раніше актуальна. Інша проблема виникає, коли індекс став фрагментованим настільки, що стає більшою проблемою, ніж допоміжною. У такому випадку відповіддю буде відновлення або скидання та відтворення індексу та повторне видалення оператора delete.


0

Як продовження відповіді Андомара вище, у мене був сценарій, коли перші 700 000 000 записів (близько 1,2 мільярда) оброблялись дуже швидко, обробляючи фрагменти в 25 000 записів в секунду (приблизно). Але тоді потрібно почати 15 хвилин, щоб зробити партію 25000. Я зменшив розмір шматка до 5000 записів, і він повернувся до своєї попередньої швидкості. Я не впевнений, який внутрішній поріг я досяг, але виправлення полягало в зменшенні кількості записів, щоб відновити швидкість.


-7

відкрийте CMD і запустіть ці команди

NET STOP MSSQLSERVER
NET START MSSQLSERVER

це перезапустить екземпляр SQL Server. спробуйте запустити ще раз після вашої команди видалення

Я маю цю команду в пакетному сценарії і час від часу запускаю її, якщо у мене виникають такі проблеми. Звичайний перезапуск ПК не буде таким самим, тому перезапуск екземпляра є найефективнішим способом, якщо у вас виникають проблеми з вашим сервером SQL.


Це не відповідає на вихідне запитання, або якщо воно відповідає, ви не пояснюєте, чому це могло б допомогти.
mjuarez
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.