Witaj Gościu! ( Zaloguj | Rejestruj )

Forum PHP.pl

 
Reply to this topicStart new topic
> genrowanie dużej liczby losowych liczb
czachor
post
Post #1





Grupa: Zarejestrowani
Postów: 897
Pomógł: 40
Dołączył: 16.12.2003
Skąd: Warszawa

Ostrzeżenie: (0%)
-----


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.


--------------------
how many SEO experts does it take to change a light bulb,lightbulb,light,bulb,lamp,lighting,switch,sex,xxx
5-Reasons-why-you-should-NEVER-fix-a-computer-for-free
Go to the top of the page
+Quote Post
Crozin
post
Post #2





Grupa: Zarejestrowani
Postów: 6 476
Pomógł: 1306
Dołączył: 6.08.2006
Skąd: Kraków

Ostrzeżenie: (0%)
-----


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.
Go to the top of the page
+Quote Post
czachor
post
Post #3





Grupa: Zarejestrowani
Postów: 897
Pomógł: 40
Dołączył: 16.12.2003
Skąd: Warszawa

Ostrzeżenie: (0%)
-----


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.

Ten post edytował czachor 13.06.2012, 14:12:19


--------------------
how many SEO experts does it take to change a light bulb,lightbulb,light,bulb,lamp,lighting,switch,sex,xxx
5-Reasons-why-you-should-NEVER-fix-a-computer-for-free
Go to the top of the page
+Quote Post
maly_swd
post
Post #4





Grupa: Zarejestrowani
Postów: 744
Pomógł: 118
Dołączył: 14.02.2009
Skąd: poziome

Ostrzeżenie: (0%)
-----


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.  



Ten post edytował maly_swd 13.06.2012, 19:57:12


--------------------
śmieszne obrazki
Kryzys: Ser jem spleśniały, wino piję stare i samochód mam bez dachu..
Go to the top of the page
+Quote Post
uupah5
post
Post #5





Grupa: Zarejestrowani
Postów: 207
Pomógł: 18
Dołączył: 4.09.2010
Skąd: warszawa

Ostrzeżenie: (0%)
-----


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
Go to the top of the page
+Quote Post
maly_swd
post
Post #6





Grupa: Zarejestrowani
Postów: 744
Pomógł: 118
Dołączył: 14.02.2009
Skąd: poziome

Ostrzeżenie: (0%)
-----


autoincrement nie jest ujemny, ale to nie problem aby obejsc;) wystarczy max(id)/2-id i mamy odpowiednia liczbe


--------------------
śmieszne obrazki
Kryzys: Ser jem spleśniały, wino piję stare i samochód mam bez dachu..
Go to the top of the page
+Quote Post

Reply to this topicStart new topic
1 Użytkowników czyta ten temat (1 Gości i 0 Anonimowych użytkowników)
0 Zarejestrowanych:

 



RSS Aktualny czas: 19.08.2025 - 20:30