2013-10-18 12 views
14

Mam dwa stoły. Nazwijmy je KEY i VALUE.
KLUCZ jest mały, zawiera około 1.000.000 rekordów.
VALUE jest ogromne, powiedzmy 1.000.000.000 rekordów.Usuwanie ogromnych ilości danych z ogromnego stołu

Pomiędzy nimi jest połączenie takie, że każdy KLUCZ może mieć wiele WARTOŚCI. To nie jest klucz obcy, ale w zasadzie to samo znaczenie.

DDL wygląda to

create table KEY (
key_id int, 
primary key (key_id) 
); 

create table VALUE (
key_id int, 
value_id int, 
primary key (key_id, value_id) 
); 

Teraz mój problem. Około połowa wszystkich identyfikatorów key_ids w VALUE została usunięta z KEY i muszę je usunąć w uporządkowany sposób, podczas gdy obie tabele nadal znajdują się pod dużym obciążeniem.

byłoby łatwe do zrobienia

delete v 
    from VALUE v 
    left join KEY k using (key_id) 
where k.key_id is null; 

Jednak, jako że nie wolno mieć limit na multi tabeli usuwać Nie podoba mi się takie podejście. Takie usuwanie trwałoby kilka godzin, a to uniemożliwia dezaktywację usunięć.

Innym rozwiązaniem jest utworzenie kursora, aby znaleźć wszystkie brakujące identyfikatory key_ids i usunąć je jeden po drugim z ograniczeniem. Wydaje się to bardzo powolne i rodzajem odwrotu.

Czy są jeszcze jakieś opcje? Jakieś fajne sztuczki, które mogłyby pomóc?

Dzięki.

+1

Czasami "GDZIE NIE ZNAJDUJE SIĘ" jest szybsze niż "LEWY DOŁĄCZ [...] JEST NIŻĄ", ale nie jest w tym przypadku pewne (http://stackoverflow.com/questions/6777910/sql-performance-on-left -outer-join-vs-not-exists). Mam nadzieję, że to pomoże! –

+0

masz na myśli to, że już usunąłeś klucze, a teraz chcesz usunąć wpisy osierocone w VALUE, prawda? – newtover

+0

W porządku, przynajmniej to byłby dokładnie ten sam problem :) –

Odpowiedz

4

Co z tym, że ma limit?

delete x 
    from `VALUE` x 
    join (select key_id, value_id 
      from `VALUE` v 
      left join `KEY` k using (key_id) 
     where k.key_id is null 
     limit 1000) y 
    on x.key_id = y.key_id AND x.value_id = y.value_id; 
+0

To wydaje się działać. Jest wolniejszy niż to, na co liczyłem, ale jest bardzo prosty i z moich pomiarów może być wystarczająco szybki. –

+4

To będzie szkodzić wydajności jako 1) łączenie dwóch dużych tabel może być powolne; 2) zagnieżdżony SELECT musi skanować więcej niż 1000 x N razy wierszy, aby znaleźć "pierwsze" 1000 wierszy do usunięcia; 3) ostatnie 999 wierszy będzie najwolniejsze, ponieważ uruchomi dwa pełne skanowanie indeksu bez wcześniejszego wyjścia; 4) usuniętych wierszy w VALUE może być bardzo losowo umieszczone w tabeli, które IO prawdopodobnie nie będą sekwencyjne –

+0

Masz rację, nadal wydaje się być szybsze niż kursor we wszystkich wierszach, ponieważ dane nie muszą być pobierane. Również KEY nie jest duży, tylko milion wierszy. –

1

może być zamiast granica podzielić całego zestawu rzędach na małe części KEY_ID:

delete v 
    from VALUE v 
    left join KEY k using (key_id) 
where k.key_id is null and v.key_id > 0 and v.key_id < 100000; 

następnie usunąć rzędy z KEY_ID w 100000..200000 i tak dalej.

+2

Nie ma nic, co powiedzieliby, że key_id 100001 nie będzie miał 1 miliona value_ids z nim związanych, a to za dużo dla jednego delete. –

+0

Odpowiedź jest najlepsza na MySQL, która ma minimalne wyszukiwanie indeksu, skanowanie tabeli i dostęp do dysku IO. Jeśli Twój stół jest obciążony dużym ryzykiem, włamujesz się do mniejszej transakcji, aby zapobiec opóźnieniom slave i blokowaniu blokad. –

1

Możesz spróbować usunąć w oddzielnych partiach transakcyjnych. To jest dla MSSQL, ale powinno być podobne.

declare @i INT 
declare @step INT 
set @i = 0 
set @step = 100000 

while (@i< (select max(VALUE.key_id) from VALUE)) 
BEGIN 
    BEGIN TRANSACTION 
    delete from VALUE where 
    VALUE.key_id between @i and @[email protected] and 
    not exists(select 1 from KEY where KEY.key_id = VALUE.key_id and KEY.key_id between @i and @[email protected]) 

    set @i = (@[email protected]) 
    COMMIT TRANSACTION 
END 
0

Czy masz SLAVE lub środowisko Dev/Test z tymi samymi danymi?

Pierwszym krokiem jest, aby dowiedzieć się o dystrybucję danych, jeśli martwisz się o danym kluczu posiadający 1 milion value_ids

SELECT v.key_id, COUNT(IFNULL(k.key_id,1)) AS cnt 
FROM `value` v LEFT JOIN `key` k USING (key_id) 
WHERE k.key_id IS NULL 
GROUP BY v.key_id ; 

Objaśnienie dla powyższego zapytania jest znacznie lepsza niż dodawanie

ORDER BY COUNT(IFNULL(k.key_id,1)) DESC ; 

Ponieważ nie masz partycjonowania na key_id (zbyt wiele partycji w twoim przypadku) i chcesz zachować bazę danych uruchomioną podczas procesu usuwania, opcja ta polega na usunięciu w uchwytach ze SLEEP() między różnymi usuwaniemi key_id, aby uniknąć przytłaczającego serwera. Nie zapomnij pilnować swoich dzienników binarnych, aby uniknąć napełniania dysku.

Najszybszym sposobem jest: aplikacja

  1. Zatrzymaj więc danych nie jest zmieniany.
  2. przegubowe KEY_ID i value_id z tabeli wartości z tylko łączenie KEY_ID w klucza tabeli stosując

    mysqldump wartość YOUR_DATABASE_NAME --where = "KEY_ID w (wybierz KEY_ID z YOUR_DATABASE_NAME.key)" --lock wszystko - opt --quick --quote-names --skip-rozszerzony-insert> VALUE_DATA.txt

  3. Obetnij WARTOŚĆ stół

  4. dane obciążenia wywożone w kroku 2
  5. Uruchom aplikację

Jak zawsze, spróbuj tego w środowisku Dev/Test z danymi Prod i tą samą infrastrukturą, aby móc obliczyć czas przestoju.

Mam nadzieję, że to pomoże.

+0

Być może nie czytam tego dobrze, ale między krokiem 1 i 5 wydaje się, że baza danych praktycznie nie działa, a aplikacje nie mają dostępu do danych. Nie mogę tego zrobić, zarówno cała baza danych, jak i te dwie tabele muszą być uruchomione podczas całego procesu. –

+0

Tak. Inne opcje to podział tabeli VALUE na podstawie key_id, ale będzie to zbyt wiele partycji w twoim przypadku LUB pętla poprzez usuwanie wierszy na podstawie key_id i umieścić SLEEP() w pętli, jak wyjaśniono. Czy dowiedziałeś się, ile key_ids musisz usunąć z tabeli VALUE? – Parag

+0

To około połowa wszystkich wartości, które należy usunąć. Tabeli nie można podzielić na partycje, w związku z czym wymaga ona całkowitego przebudowania tabeli, a gdy tak się stanie, tabela jest niedostępna i niedostępna. To pytanie dotyczy usuwania danych bez usuwania tabeli lub bazy danych. Istnieje wiele prostszych podejść, jeśli tak było, jak na przykład jedno wielkie usunięcie. –

1

Utwórz tymczasowy stół!

drop table if exists batch_to_delete; 
create temporary table batch_to_delete as 
select v.* from `VALUE` v 
left join `KEY` k on k.key_id = v.key_id 
where k.key_id is null 
limit 10000; -- tailor batch size to your taste 

-- optional but may help for large batch size 
create index batch_to_delete_ix_key on batch_to_delete(key_id); 
create index batch_to_delete_ix_value on batch_to_delete(value_id); 

-- do the actual delete 
delete v from `VALUE` v 
join batch_to_delete d on d.key_id = v.key_id and d.value_id = v.value_id; 
2

Najpierw sprawdź swoje dane. Znajdź klucze, które mają zbyt wiele wartości do usunięcia "szybko". Następnie sprawdź, które razy w ciągu dnia masz najmniejsze obciążenie systemu. Wykonaj usunięcie "złych" kluczy w tym czasie. Co do reszty, zacznij usuwać je jeden po drugim z pewnym przestojem między usuwaniami, aby nie wywierał dużego nacisku na bazę danych podczas jej wykonywania.

1

Dla mnie jest to rodzaj zadania, którego postęp chciałbym zobaczyć w pliku dziennika. I uniknąłbym rozwiązania tego w czystym SQL, używałbym skryptów w Pythonie lub innym podobnym języku. Inną rzeczą, która przeszkadza mi w tym, jest to, że wiele LEWYCH DOŁĄCZÓW z GDZIE NIE JEST NULL pomiędzy tabelami może spowodować niepożądane blokady, więc uniknęłbym również JOIN.

Oto niektóre pseudo kod:

max_key = select_db('SELECT MAX(key) FROM VALUE') 
while max_key > 0: 
    cur_range = range(max_key, max_key-100, -1) 
    good_keys = select_db('SELECT key FROM KEY WHERE key IN (%s)' % cur_range) 
    keys_to_del = set(cur_range) - set(good_keys) 
    while 1: 
     deleted_count = update_db('DELETE FROM VALUE WHERE key IN (%s) LIMIT 1000' % keys_to_del) 
     db_commit 
     log_something 
     if not deleted_count: 
      break 
    max_key -= 100 

To nie powinno niepokoić reszty systemu bardzo dużo, ale może trwać długo. Kolejną kwestią jest zoptymalizowanie tabeli po usunięciu wszystkich tych wierszy, ale to już inna historia.

5

Bezpośrednio z MySQL documentation

Jeśli usuwasz wiele wierszy z dużym stołem, można przekroczyć blokady rozmiar tabeli dla tabeli InnoDB.Aby uniknąć tego problemu, lub po prostu aby zminimalizować czas, że tabela pozostaje zablokowana, następujące strategia (który nie używa DELETE w ogóle) może być pomocne:

Zaznacz wiersze nie mogą być usunięte do pustego stołu który ma taką samą strukturę jak oryginalnej tabeli:

INSERT INTO t_copy SELECT * FROM t WHERE ... ; 

Korzystanie RENAME tabeli, aby przenieść atomowo oryginalny stolik z drogi i zmień nazwę kopię oryginalnej nazwie:

RENAME TABLE t TO t_old, t_copy TO t; 

Kropla oryginalna tabela:

DROP TABLE t_old; 

Żadne inne sesje mogą uzyskać dostęp do tabel zaangażowane podczas RENAME TABELA sporządzi, więc operacja zmiany nazwy nie podlega współbieżności problemy. Zobacz Rozdział 12.1.9, "Składnia tabeli zmian".

Więc w twoim przypadku może to zrobić

INSERT INTO value_copy SELECT * FROM VALUE WHERE key_id IN 
    (SELECT key_id FROM `KEY`); 

RENAME TABLE value TO value_old, value_copy TO value; 

DROP TABLE value_old; 

I zgodnie z tym co pisali here operacji zmiany nazwy jest szybkie i liczba rekordów nie ma wpływu na to.

+1

Problem polega na tym, że wstawienie pół miliarda rekordów do pobliskiej tabeli zajmie trochę czasu i zatwierdzenie transakcji. W przeciwnym razie wystąpi problem z synchronizacją tabeli kopiowania z oryginalną, jeśli ta ostatnia jest pod dużym obciążeniem i jest aktualizowana. – newtover

+0

Właśnie zrobiłem mały test, żeby się upewnić. A MySQL skopiuje wszystkie INSERTs preformy wszystkich UPDATE'ów, które zostaną wykonane podczas wykonywania tego zapytania INSERT ... SELECT. – Gustek

+0

Wszystkie wiersze, które powinny pozostać w tabeli, tj. Nie usunięte, są stale aktualizowane i ta metoda zmusiłaby mnie do blokowania zapisów, dopóki nie zostanie wykonana, ponieważ transakcja ta utrzymywałaby blokadę odczytu (i blokowanie zapisu) przez naprawdę długi czas. –

21

Każde rozwiązanie, które próbuje usunąć tak dużo danych w ramach jednej transakcji, spowoduje przeciążenie segmentu wycofywania i spowoduje wiele problemów z wydajnością.

Dobre narzędzie do pomocy to pt-archiver. Wykonuje operacje przyrostowe na umiarkowanych partiach wierszy, tak skutecznie, jak to możliwe. pt-archiver można kopiować, przenosić lub usuwać wiersze w zależności od opcji.

Dokumentacja zawiera przykład usuwanie osieroconych wierszy, który jest dokładnie twój scenariusz:

pt-archiver --source h=host,D=db,t=VALUE --purge \ 
    --where 'NOT EXISTS(SELECT * FROM `KEY` WHERE key_id=`VALUE`.key_id)' \ 
    --limit 1000 --commit-each 

Realizująca będzie to znacznie dłużej trwa usuwanie danych, ale to nie będzie wykorzystywać zbyt wiele zasobów i bez przerywanie usługi w istniejącej bazie danych. Użyłem go z powodzeniem do usunięcia setek milionów wierszy przestarzałych danych.

pt-archiver jest częścią zestawu Percona Toolkit for MySQL, darmowego (GPL) skryptów, które pomagają w wykonywaniu typowych zadań z MySQL i kompatybilnymi bazami danych.

1

Jeśli kolumny docelowe są poprawnie indeksowane to powinno iść szybko,

DELETE FROM `VALUE` 
WHERE NOT EXISTS(SELECT 1 FROM `key` k WHERE k.key_id = `VALUE`.key_id) 
-- ORDER BY key_id, value_id -- order by PK is good idea, but check the performance first. 
LIMIT 1000 

Alter limit od 10 do 10000, aby uzyskać akceptowalną wydajność i uruchom go kilka razy.

również wziąć pod uwagę, że masa ta Usuwa wykona zamki i kopii zapasowych dla każdego wiersza .. wielokrotnością czasu wykonania dla każdego wiersza kilka razy ...

Istnieją pewne zaawansowane metody, aby temu zapobiec, ale Najłatwiejsze obejście to tylko transakcja dotycząca tego zapytania.

0

Jestem ciekawy, jaki byłby efekt dodania nieunikalnego indeksu na key_id w tabeli VALUE. Selektywność wcale nie jest wysoka (~ 0.001), ale jestem ciekawa, jak wpłynęłoby to na wydajność łączenia.

0

Dlaczego nie podzielisz tabeli VALUE na kilka według niektórych reguł, takich jak moduł key_id, o mocy 2 (na przykład 256)?

Powiązane problemy