SQL Server, Эффективно отбрасывающий группу строк с миллионами и миллионов строк

Я недавно задал этот вопрос: семя идентификационных данных доли SQL MS среди таблиц (Многие люди задались вопросом почему),

У меня есть следующее расположение таблицы:

Таблица: звезды
starId bigint
categoryId bigint
starname varchar (200)

Но моя проблема состоит в том, что у меня есть миллионы и миллионы строк. Таким образом, когда я хочу удалить звезды из таблицы Stars, это слишком интенсивно на SQL Server.

Я не могу использовать созданный в разделении на 2005 +, потому что у меня нет корпоративной лицензии.

Когда я действительно удаляю, хотя, я всегда удаляю целую категорию Id за один раз.

Я думал о выполнении дизайна как это:

Таблица: Star_1
starId bigint
CategoryId bigint constaint rock=1
starname varchar (200)

Таблица: Star_2
starId bigint
CategoryId bigint constaint rock=2
starname varchar (200)

Таким образом я могу удалить целую категорию и следовательно миллионы строк в O (1) путем выполнения простой таблицы отбрасывания.

Мой вопрос, действительно ли это - проблема, чтобы иметь сотни тысяч таблиц в Вашем SQL Server? Понижение O (1) чрезвычайно желательно мне. Возможно, существует совершенно другое решение, о котором я не думаю?

Править:

Звезда когда-либо изменяется, после того как она вставляется? Нет.

Необходимо ли когда-либо запрашивать через звездообразные категории? Я никогда не должен запрашивать через звездообразные категории.

При поиске данных по конкретной звезде Вы знали бы который таблица запросить? Да

При вводе данных, как приложение решит который таблица поместить данные в? Вставка звездообразных данных сделана внезапно в запуске, когда categoryId создается.

Сколькими там будут категории? Можно предположить, что будут бесконечные звездообразные категории. Скажем, до 100 звездообразных категорий в день и до 30 звездообразных категорий, не нужных в день.

Действительно необходимо ли удалить целую категорию или только звезду что данные, измененные для? Да целая звездообразная категория.

Вы попытались удалить в пакетах? Да мы делаем это сегодня, но это не достаточно хорошо. передозировка достаточно.

Другая техника является меткой запись для удаления? Нет никакой потребности отметить звезду, как удалено, потому что мы знаем, что целая звездообразная категория имеет право быть удаленной.

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

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

Не навсегда, но пока ручной запрос для удаления категории выпущен. Раз так, что % времени, которое происходит? Не то, чтобы часто.

Какое расположение диска Вы используете? Единственное устройство хранения данных группы файлов и никакое разделение в настоящее время.

Можно ли использовать sql предприятие? Нет. Существуют многие люди, которые запускают это программное обеспечение, и у них только есть sql стандарт. Это за пределами их бюджета для получения мс sql предприятие.

10
задан Community 23 May 2017 в 10:27
поделиться

13 ответов

У меня вопрос, а есть ли проблема иметь сотни тысяч таблиц в вашем SQL Server?

Да. Наличие такого количества таблиц в вашем SQL Server - огромная проблема. Каждый объект должен отслеживаться SQL Server как метаданные, и как только вы включаете индексы, ссылочные ограничения, первичные ключи, значения по умолчанию и так далее, вы говорите о миллионах объектов базы данных.

Хотя SQL Server теоретически может обрабатывать 2 32 объектов, будьте уверены, что он начнет деформироваться под нагрузкой гораздо раньше.

И если база данных не рухнет, ваши разработчики и ИТ-персонал почти наверняка это сделают. Я нервничаю, когда вижу больше тысячи столов или около того; покажите мне базу данных с сотнями тысяч , и я с криком убежу.

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

  • Написание эффективных запросов (как вы SELECT несколько категорий?)
  • Сохранение уникальных идентификаторов (как вы уже обнаружили)
  • Поддержание ссылочной целостности (если вам не нравится управлять 300 000 внешних ключей)
  • Выполнять обновления с диапазоном значений
  • Написать чистый код приложения
  • Вести любую историю
  • Обеспечьте надлежащую безопасность (кажется очевидным, что пользователи должны иметь возможность инициировать эти создание / удаление - очень опасно)
  • Кэширование должным образом - 100 000 таблиц означают, что 100 000 различных планов выполнения, все конкурируют за одну и ту же память, что вы, вероятно, не делаете У меня достаточно;
  • Наймите администратора базы данных (будьте уверены, они уйдут, как только увидят вашу базу данных).

С другой стороны, не проблема иметь сотни тысяч строк или даже миллионов строк в одном table - именно так были разработаны SQL Server и другие СУБД SQL, и они очень хорошо оптимизированы для этого случая.

Понижение O (1) для меня крайне желательно. Может быть, есть совершенно другое решение, о котором я не думаю?

Типичное решение проблем производительности в базах данных в порядке предпочтения:

  • Запустить профилировщик, чтобы определить, какие части запроса самые медленные;
  • ] Если возможно, улучшите запрос (т. Е.путем устранения предикатов, не поддерживающих саргетинг);
  • Нормализовать или добавить индексы для устранения этих узких мест;
  • Денормализовать при необходимости (обычно не применимо к удалению);
  • Если задействованы каскадные ограничения или триггеры, отключите их на время транзакции и продуть каскады вручную.

Но реальность здесь такова, что вам не требуется «решение».

«Миллионы и миллионы строк» ​​- это не так много в базе данных SQL Server. Очень быстро удалить несколько тысяч строк из таблицы миллионов, просто проиндексировав столбец, из которого вы хотите удалить - в данном случае CategoryID . SQL Server может сделать это, не беспокоясь.

Фактически, удаления обычно имеют сложность O (M log N) (N = количество строк, M = количество строк для удаления). Чтобы добиться времени удаления O (1), вам придется пожертвовать почти всеми преимуществами, которые SQL Server предоставляет в первую очередь.

O (M log N) может быть не таким быстрым, как O (1), но замедление, о котором вы говорите (несколько минут на удаление) , должно иметь вторичную причину. Цифры не складываются, и чтобы продемонстрировать это, я пошел дальше и провел тест:


Схема таблицы:

CREATE TABLE Stars
(
    StarID int NOT NULL IDENTITY(1, 1)
        CONSTRAINT PK_Stars PRIMARY KEY CLUSTERED,
    CategoryID smallint NOT NULL,
    StarName varchar(200)
)

CREATE INDEX IX_Stars_Category
ON Stars (CategoryID)

Обратите внимание, что эта схема даже не оптимизирована для операций DELETE , это довольно заурядная схема таблицы, которую вы можете увидеть на сервере SQL. Если эта таблица не имеет отношений, то нам не нужен суррогатный ключ или кластерный индекс (или мы могли бы поместить кластерный индекс в категорию). Я вернусь к этому позже.

Пример данных:

Это заполнит таблицу 10 миллионами строк с использованием 500 категорий (т. Е. Количество элементов 1: 20 000 для каждой категории). Вы можете настроить параметры, чтобы изменить объем данных и / или мощность.

SET NOCOUNT ON

DECLARE
    @BatchSize int,
    @BatchNum int,
    @BatchCount int,
    @StatusMsg nvarchar(100)

SET @BatchSize = 1000
SET @BatchCount = 10000
SET @BatchNum = 1

WHILE (@BatchNum <= @BatchCount)
BEGIN
    SET @StatusMsg =
        N'Inserting rows - batch #' + CAST(@BatchNum AS nvarchar(5))
    RAISERROR(@StatusMsg, 0, 1) WITH NOWAIT

    INSERT Stars2 (CategoryID, StarName)
        SELECT
            v.number % 500,
            CAST(RAND() * v.number AS varchar(200))
        FROM master.dbo.spt_values v
        WHERE v.type = 'P'
        AND v.number >= 1
        AND v.number <= @BatchSize

    SET @BatchNum = @BatchNum + 1
END

Скрипт профиля

Самый простой из них ...

DELETE FROM Stars
WHERE CategoryID = 50

Результаты:

Это было протестировано на машине 5-летней давности , работающей, IIRC, 32-битной двойной -ядер AMD Athlon и дешевый SATA-накопитель на 7200 об / мин.

Я запускал тест 10 раз, используя разные CategoryID. Самое медленное время (холодный кеш) было около 5 секунд. Самое быстрое время - 1 секунда.

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

Но мы можем сделать лучше ...

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

Сейчас, как правило, кластеризованные индексы на неуникальных / неуникальныхпоследовательные столбцы - не лучшая практика. Но мы здесь просто проводим сравнительный анализ, так что сделаем это в любом случае:

CREATE TABLE Stars
(
    CategoryID smallint NOT NULL,
    StarName varchar(200)
)

CREATE CLUSTERED INDEX IX_Stars_Category
ON Stars (CategoryID)

Запустите тот же генератор тестовых данных на этом (с ошеломляющим количеством разбиений страниц), и то же удаление заняло в среднем всего 62 миллисекунды и 190 из холодного кеша (выброс). И для справки, если индекс делается некластеризованным (без кластеризованного индекса), то время удаления увеличивается в среднем до 606 мс.

Заключение:

Если вы видите время удаления несколько минут или даже несколько секунд , то что-то очень и очень неправильно .

Возможные факторы:

  • Статистика не обновляется (здесь не должно быть проблем, но если это так, просто запустите sp_updatestats );

  • Отсутствие индексации (хотя Любопытно, что удаление индекса IX_Stars_Category в первом примере фактически приводит к более быстрому общему удалению, потому что сканирование кластерного индекса выполняется быстрее, чем удаление некластеризованного индекса);

  • Неправильно - выбранные типы данных. Если у вас только миллионов строк, в отличие от миллиардов , то вам не нужен bigint на StarID . Вам определенно он не нужен в CategoryID - если у вас меньше 32 768 категорий, вы можете даже использовать smallint . Каждый байт ненужных данных в каждой строке увеличивает стоимость ввода-вывода.

  • Конфликт за блокировку.Может быть, проблема вовсе не в скорости удаления; может быть, какой-то другой сценарий или процесс удерживает блокировки на Star строках, а DELETE просто сидит и ждет, пока они отпустят.

  • Чрезвычайно плохое оборудование. Мне удалось запустить это без каких-либо проблем на довольно паршивой машине, но если вы запускаете эту базу данных на Presario 90-х годов или на какой-то подобной машине, которая абсурдно не подходит для размещения экземпляра SQL Server, и она сильно загружена , то вы, очевидно, столкнетесь с проблемами.

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

Я, честно говоря, не могу думать ни о каких других возможностях. Удаление в SQL Server только не так уж и медленно .


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

Если это все еще не работает » Если вам не поможет, то я могу предложить следующие дополнительные предложения:

  • Обновление до SQL Server 2008, которое дает вам множество вариантов сжатия , которые могут значительно улучшить производительность ввода-вывода;

  • Рассмотрите возможность предварительной -сжатие данных по категории Star в компактный сериализованный список (с использованием класса BinaryWriter в .NET) и сохранение его в столбце varbinary . Таким образом, у вас может быть одна строка для каждой категории. Это нарушает правила 1NF, но поскольку вы, похоже, в любом случае ничего не делаете с отдельными данными Star из базы данных, я сомневаюсь, что вы много потеряете.

  • Рассмотрите возможность использования нереляционной базы данных или формата хранения, например db4o или Cassandra . Вместо реализации известного анти-шаблона базы данных (печально известного «дампа данных») используйте инструмент, который на самом деле разработан для такого типа хранилища и шаблона доступа.

34
ответ дан 3 December 2019 в 13:32
поделиться

Наличие отдельных таблиц - это разбиение на разделы - вы просто управляете ими вручную и не получаете никакой помощи в управлении или унифицированного доступа (без представления или секционированного представления).

Является ли Enterprise Edition дороже, чем стоимость отдельного построения и обслуживания схемы секционирования?

Альтернативы длительному удалению также включают заполнение таблицы замены идентичной схемой и простое исключение строк, подлежащих удалению. а затем поменять местами таблицу с помощью sp_rename.

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

2
ответ дан 3 December 2019 в 13:32
поделиться

Возможно, в таблице Stars установите PK на некластеризованный и добавьте кластерный индекс для categoryid .

Помимо этого, хорошо ли настроен сервер с точки зрения лучших практик для повышения производительности? Это использование отдельных физических дисков для данных и журналов, без использования RAID5 и т. Д.

1
ответ дан 3 December 2019 в 13:32
поделиться

Вы должны удалить их? Часто лучше просто установить битовый столбец IsDeleted на 1, а затем выполнять фактическое удаление асинхронно в нерабочее время.

Редактировать:

Это снимок в темноте, но добавление кластерного индекса на CategoryId может ускорить удаление. Это также может отрицательно повлиять на другие запросы. Вы можете это проверить?

4
ответ дан 3 December 2019 в 13:32
поделиться

Это был старый метод в SQL 2000, секционированные представления, и он остается допустимым вариантом для SQL 2005. Проблема действительно возникает в от наличия большого количества таблиц и связанных с ними накладных расходов на обслуживание.

Как вы сказали, разбиение на разделы - это функция предприятия, но она разработана для этого крупномасштабного удаления данных / эффекта скользящего окна.

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

2
ответ дан 3 December 2019 в 13:32
поделиться

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

Действительно ли вам нужно удалить всю категорию или только звездочку, для которой изменились данные? А нужно ли вообще удалять, может, нужно только обновить информацию.

Пробовали ли вы удаление группами (около 1000 записей за раз в цикле). Часто это намного быстрее, чем удаление миллиона записей за один оператор удаления. Это также часто предотвращает блокировку таблицы во время удаления.

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

Учитывая ваши ответы, я думаю, что ваше предложение может быть разумным.

0
ответ дан 3 December 2019 в 13:32
поделиться

Я знаю, что это немного касательно, но действительно ли SQL Server (или любая реляционная база данных) является хорошим инструментом для этой работы? Какие возможности реляционной базы данных вы на самом деле используете?

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

Мне кажется, вы используете базовые функции запросов SELECT?

0
ответ дан 3 December 2019 в 13:32
поделиться

Что вы имеете в виду, когда говорите, что удаление миллионов строк «слишком сложно для SQL-сервера»? Вы имеете в виду, что файл журнала слишком сильно увеличивается во время удаления?

Все, что вам нужно сделать, это выполнить удаление пакетами фиксированного размера:

DECLARE @i INT
SET @i = 1

WHILE @i > 0
BEGIN
    DELETE TOP 10000 FROM dbo.SuperBigTable
        WHERE CategoryID = 743
    SELECT @i = @@ROWCOUNT
END

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

Моя единственная другая рекомендация - убедиться, что у вас есть соответствующий индекс в CategoryId. Я мог бы даже порекомендовать, чтобы это был кластерный индекс.

1
ответ дан 3 December 2019 в 13:32
поделиться

Просто взяв ваше представление о множестве таблиц - как вы можете понять это ...

А как насчет использования динамических запросов.

  1. создать таблицу категорий, у которых есть столбец идентификатора category_id.
  2. создать триггер на вставку для этой сказки - в ней создать таблицу для звезд с именем, динамически созданным из category_id.
  3. создать триггер на удаление - в него сбросить соответствующую таблицу звезд также с помощью динамически созданного sql.
  4. для выбора звезд конкретной категории можно использовать функцию, возвращающую таблицу. Он будет принимать category_id в качестве параметра и возвращать результат также через динамический запрос.
  5. для вставки звездочек новой категории вы сначала вставляете новую строку в таблицу категорий, а затем вставляете звезды в соответствующую таблицу.

Другое направление, в котором я хотел бы провести некоторые исследования, - это использование столбца с типом xml для хранения данных о звездах. Основная идея здесь в том, что если вам нужно управлять звездами только по категориям, то почему бы не хранить все звезды конкретной категории в одной ячейке таблицы в формате xml. К сожалению, я совершенно не могу представить себе, каковы будут результаты такого решения.

Оба этих варианта похожи на идеи в мозговом штурме.

0
ответ дан 3 December 2019 в 13:32
поделиться

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

Никогда не будет никаких удалений миллионов строк, которые происходят так же быстро, как удаление таблицы, без использования секций.

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

  1. Создайте новую таблицу звездочек для каждой новой категории .
  2. Подождите, пока истечет период времени, когда вы решите, сохранять ли звезды для категории или нет.
  3. Сверните записи в основную таблицу звезд, если вы планируете их хранить.
  4. Бросьте стол.

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

В конечном счете, для категорий, которые вы сохраняете, вы удваиваете объем работы, но дополнительная работа распределяется по времени.Вставки в конец кластеризованного индекса могут быть менее заметны для пользователей, чем удаления из середины. Однако для тех категорий, которые вы не сохраняете, вы экономите массу времени.

Даже если вы технически не экономите работу, восприятие часто оказывается более серьезной проблемой.

0
ответ дан 3 December 2019 в 13:32
поделиться

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

Невозможно удалить строки из таблицы без включения транзакции, хотя можно усечь таблицу с помощью команды TRUNCATE. Однако это приведет к удалению всех строк в таблице без каких-либо условий.

Я могу предложить следующие предложения:

  1. Переключитесь на нетранзакционную базу данных или, возможно, на плоские файлы. Похоже, вам не нужна атомарность транзакционной базы данных.

  2. Попытайтесь сделать следующее. После каждого удаления x (в зависимости от размера) выполните следующую инструкцию

BACKUP LOG WITH TRUNCATE_ONLY;

Это просто усекает журнал транзакций, остается место для пополнения журнала. Однако я не уверен, сколько времени это добавит к операции.

1
ответ дан 3 December 2019 в 13:32
поделиться

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

Также вы могли бы описать отношения в таблице.

1
ответ дан 3 December 2019 в 13:32
поделиться

Я не получил ответа на свой комментарий к исходному сообщению, поэтому я делаю некоторые предположения ...

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

Вы можете бесплатно использовать управляемую базу данных ESE , которая поставляется с каждой версией Windows.

Используйте объект PersistentDictionary и таким образом отслеживайте пары звездочка, звездочка. Если вам нужно удалить категорию, просто удалите объект PersistentDictionary для этой категории.

PersistentDictionary<int, string> starsForCategory = new PersistentDictionary<int, string>("Category1");

Это создаст базу данных под названием «Category1», в которой вы можете использовать стандартные методы словаря .NET (add, exists, foreach и т. Д.).

0
ответ дан 3 December 2019 в 13:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: