Pomoc - Szukaj - Użytkownicy - Kalendarz
Pełna wersja: genrowanie dużej liczby losowych liczb
Forum PHP.pl > Forum > Bazy danych > MySQL
czachor
Witam,

mam taki problem: chcę wrzucić do tabeli 16 milionów rekordów zawierających losowe unikalne liczby z wybranego zakresu.

Tabela jest prosta:
  1. CREATE TABLE `codes` (
  2. `code_id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  3. `code` bigint(20) UNSIGNED NOT NULL,
  4. `is_used` tinyint(1) NOT NULL DEFAULT '0',
  5. PRIMARY KEY (`code_id`),
  6. UNIQUE KEY `code` (`code`)
  7. ) ENGINE=InnoDB DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;


I teraz... Stworzyłem funkcję, która na wejściu ma zakres (min, max) oraz ilość rekordów, a która generuje losowe liczby i zapisuje je do tabeli. Działa na zasadzie dopełnienia liczby rekordów do wymaganej ilości.
  1. DELIMITER $$
  2.  
  3. CREATE DEFINER=`root`@`localhost` FUNCTION `codes`(`minRange` BIGINT UNSIGNED, `maxRange` BIGINT UNSIGNED, `_amount` BIGINT UNSIGNED) RETURNS tinyint(1)
  4. MODIFIES SQL DATA
  5. BEGIN
  6. DECLARE pick BIGINT;
  7.  
  8.  
  9. while (SELECT count(*) FROM codes) < _amount do
  10. begin
  11. SET pick = minRange + FLOOR(RAND() * (maxRange - minRange + 1));
  12. INSERT IGNORE INTO codes (code) VALUES (pick);
  13.  
  14. end;
  15. end while;
  16.  
  17. RETURN 1;
  18. END$$
  19.  
  20. DELIMITER ;

Funkcja jest meganieoptymalna, bo za każdym przejściem pętli sprawdza, ile jest już rekordów w tabeli i kontynuuje działanie, dopóki nie zostanie osiągnięta docelowa liczba rekordów. Czas wstawiania 20 000 rekordów z zakresu 1 000 000 000 - 9 999 999 999 (zależy mi na liczbach 10-cyfrowych) to 2,5 minuty. Wstawienie 16 milionów rekordów to szacunkowo 33 godziny.


Druga funkcja działa na zasadzie wstawienia określonej liczby rekordów (nie dopełnienia):
  1. DELIMITER $$
  2. --
  3. -- Funkcje
  4. --
  5. CREATE DEFINER=`root`@`localhost` FUNCTION `codes`(`minRange` BIGINT UNSIGNED, `maxRange` BIGINT UNSIGNED, `_amount` BIGINT UNSIGNED) RETURNS tinyint(1)
  6. MODIFIES SQL DATA
  7. BEGIN
  8. DECLARE pick BIGINT;
  9. DECLARE i BIGINT;
  10. SET i = 1;
  11.  
  12.  
  13. while i <= _amount do
  14. begin
  15. SET pick = minRange + FLOOR(RAND() * (maxRange - minRange + 1));
  16. INSERT IGNORE INTO codes (code) VALUES (pick);
  17. SET i = i+1;
  18. end;
  19. end while;
  20.  
  21. RETURN 1;
  22. END$$
  23.  
  24. DELIMITER ;

Funkcja jest o niebo szybsza, 20 000 rekordów to ~1 sek. Wada: nie zawsze wstawi wymaganą liczbę rekordów, bo nie ma sprawdzania, ile jest rekordów w tabeli, a jest INSERT IGNORE, żeby uniknąć duplikatów.

To co chciałbym osiągnąć, to optymalizacja pierwszej funkcji - tak, żeby dopełniła mi tabelę do wymaganej liczby rekordów. Będę wdzięczny za wskazówki.
Crozin
1. Skorzystaj z prepared statements do dodawania rekordów - powinno jeszcze nieco przyspieszyć działanie.
2. Połącz sposób działania obu funkcji, tj. w pętli sprawdzaj ile rekordów jeszcze pozostało do dodania (COUNT(*) - _amount), następnie w pętli dodaj n rekordów, gdzie n to MIN(_amount, COUNT(*) - _amount). Dzięki temu powinno wykonywać zapytania dodania rekordów do czasu osiągnięcia wymaganej ilości.
czachor
Dzięki.
Mam jeszcze info, że jedno z szybszych rozwiązań to zapisanie liczb do pliku tekstowego, a potem załadowanie ich używając LOAD DATA INFILE. I oczywiście należy usunąć (tymczasowo) indeksy, które są aktualizowane po każdym insercie.
maly_swd
Zrob tak jak pisze Crozin

czyli robisz dwie petle:

pseldo kod zarys logiki

  1. while (SELECT count(*) FROM codes) < _amount do // tu sprawdzasz czy jest juz tyle ile chcesz
  2.  
  3. SET ile_jeszcze_dodac=_amount - (SELECT count(*) FROM codes); // tu ustawiasz ile jeszcze musisz dodac
  4. SET i=0; // zerowanie licznika
  5.  
  6. while (i < ile_jeszcze_dodac) do // tu wykonujesz petle tyle razy ile musisz dodac, i nie przejmujesz sie ze cos zduplikowane. Petla wyzej na nowo spradzi i ustai ile zabraklo
  7. begin
  8. SET pick = minRange + FLOOR(RAND() * (maxRange - minRange + 1));
  9. INSERT IGNORE INTO codes (code) VALUES (pick);
  10. i=i+1; // tu zwiekszasz licznik petli
  11. end;
  12. end while;
  13.  
  14. end while;



W php (szybko ale wymaga duzo pamieci)



  1. $tab=range($min,$max); // generuje tablice wartosci od do (czyli podajesz -99999999 do 9999999) ale to zajmuje mase pamieci
  2. shuffle($tab); // miesza ta tablice, czyli robi losowosc;p
  3. $tab=array_slice($tab, 0, $ile_liczb); // wycina od 0 do ilu tam elementow chcesz... i w $tab masz juz losowe liczby UNIKALNE, pozniej zrobienie z tego CSV to pikus
  4.  

uupah5
a odwracając problem?
tworzysz tabelę zawierającą autoincrement w pełnym zadanym zakresie a losowanie realizujesz przez select z order by rand(), ew z limit

order by rand() reklamowane jest jako niewydajne ale przy innodb z indeksem w ramie byłoby pewnie o rzędy szybkości szybsze niż te 33 godziny
maly_swd
autoincrement nie jest ujemny, ale to nie problem aby obejsc;) wystarczy max(id)/2-id i mamy odpowiednia liczbe
To jest wersja lo-fi głównej zawartości. Aby zobaczyć pełną wersję z większą zawartością, obrazkami i formatowaniem proszę kliknij tutaj.
Invision Power Board © 2001-2025 Invision Power Services, Inc.