konto usunięte

Temat: Losowanie rekordów w dużej bazie danych

Witam serdecznie,
Mam taką tabelkę:

CREATE TABLE IF NOT EXISTS `kategorie` (
`id` BIGINT(20) UNSIGNED NOT NULL,
`tytul` VARCHAR(85) COLLATE utf8_unicode_ci DEFAULT NULL,
`domena_url` VARCHAR(100) COLLATE utf8_unicode_ci DEFAULT NULL,
`enable` INT(11) NOT NULL DEFAULT '0',
`level` INT(11) NOT NULL DEFAULT '0',
`parent_id` INT(11) NOT NULL DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

--
-- Indeksy dla zrzutów tabel
--

--
-- Indexes for table `cms_kategorie`
--
ALTER TABLE `cms_kategorie`
ADD PRIMARY KEY (`bf_id`),
ADD UNIQUE KEY `bf_id` (`bf_id`),
ADD KEY `bf_id_2` (`bf_id`);

--
-- AUTO_INCREMENT for dumped tables
--

--
-- AUTO_INCREMENT dla tabeli `cms_kategorie`
--
ALTER TABLE `cms_kategorie`
MODIFY `bf_id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT;





Baza zawiera 1.5GB i polecenie ORDER by RAND() bardzo "zamula" (skrypt wykonuje się ponad 60 sekund).
Wyczytałem że jest to problem tych randów i trzeba zastosować procedury.

Kombinuję z czymś takim (nie chce mi to za bardzo działać):

DELIMITER $$
DROP PROCEDURE IF EXISTS getRandomRecords$$
CREATE PROCEDURE getRandomRecords(IN cnt INT)

BEGIN
DROP TEMPORARY TABLE IF EXISTS randomRecords;
CREATE TEMPORARY TABLE randomRecords ( `rand_id` BIGINT, `tytul` VARCHAR(85), `domena_url` VARCHAR(100) );

loop_random: LOOP
IF cnt < 1 THEN
LEAVE loop_random;
END IF;

INSERT INTO randomRecords
SELECT r1.id, r1.tytul, r3.domena_url FROM `kategorie` AS r1 JOIN
(SELECT (RAND() *
(SELECT MAX(id) FROM `kategorie`)) AS id) AS r2
WHERE r1.id >= r2.id ORDER BY r1.id ASC LIMIT 1;
SET cnt = cnt - 1;
END LOOP loop_random;

SELECT * FROM randomRecords;
END$$
DELIMITER ; </code>

Wie ktoś jak to powinno wyglądać?

Mój kod php na stronie internetowej wygląda następująco:
<code>
$miasta = Array();
$stmt = $db->prepare("select tytul, domena_url, id FROM kategorie1 WHERE enable= 1 ORDER by RAND() ASC limit 50;");
$stmt->execute();
foreach ($stmt as $rowX) {
array_push($miasta, $rowX['tytul']);
}
$stmt->closeCursor();

$produkty = Array();
$stmt = $db->prepare("select tytul, domena_url, id FROM kategorie2 WHERE enable= 1 ORDER by RAND() ASC limit 50;");
$stmt->execute();
foreach ($stmt as $rowX) {
array_push($produkty, $rowX['tytul']);
}
$stmt->closeCursor();

$res = array();
foreach($miasta as $val) {
$nazwa_miasta = baza_odczyt($val .' ' . array_shift($produkty));
echo "<a href='".$portal_url."' title='".$nazwa_miasta."'>".$nazwa_miasta."</a>, ";
}


Wie ktoś może jak to powinno wyglądać?

konto usunięte

Temat: Losowanie rekordów w dużej bazie danych

Ze składni sądzę, że może to być firebird...?

konto usunięte

Temat: Losowanie rekordów w dużej bazie danych

Tak z zupełnie innej beczki. Do czego z tego się będzie strzelać? Jeśli chodzi o losowanie, to zrób sobie "widok" z numerem kolejnym a potem wylosuj numer linii. Może będzie bardziej ekonomiczne a jakieś RAND() zostanie użyte...
Czasami warto zmieniać punkt odniesienia. :)Ten post został edytowany przez Autora dnia 02.10.15 o godzinie 20:57

konto usunięte

Temat: Losowanie rekordów w dużej bazie danych

to jest MySQL. potrzebuję z bazy wydobyć losowe rekordy.... i ten rand właśnie trwa wieki ;/

konto usunięte

Temat: Losowanie rekordów w dużej bazie danych

A może tak będzie szybciej?

Najpierw na podstawie liczby wierszy w tabeli wybieramy losowy:

$row - wynik zapytania SELECT ROUND(RAND() * (SELECT COUNT(*) FROM table))

a następnie budujemy zapytanie:
$query = 'SELECT * FROM table LIMIT ' . $row. ', 1';
...
Janusz Skudrzyk

Janusz Skudrzyk Członek zarządu,
weblabs.pl

Temat: Losowanie rekordów w dużej bazie danych

Jeżeli liczy się tylko losowość miejsca w tabeli i mogą być kolejne rekordy, to po stronie php losujemy liczbę (np. z max(id)), a następnie: select kolumny from tabela where id > $liczba limit $ilosc. Jedyny problem w takim wylosowaniu $liczba, żeby nie wyjść z $liczba+$ilosc poza ostatni rekord (a w razie czego, to można odpalić drugie zapytanie dociągające brakujące rekordy z jakiegoś wcześniejszego miejsca).

Jeżeli losowość ma być "podtrzymywana" przez dłuższy czas (np. przez godzinę), to robimy sobie dodatkową tabelę z identyfikatorami rekordów i przy pomocy crona odpalamy losowanie.

konto usunięte

Temat: Losowanie rekordów w dużej bazie danych

losowanie musi za każdym razem w miarę możliwości dać inne wyniki :)

Zrobiłem takie zapytanie:
SELECT tytul, domena_url FROM kategorie WHERE enable = 1 and RAND()<(SELECT ((50/COUNT(*))*50) FROM kategorie) ORDER BY RAND() LIMIT 50 ;

I skrypt działa jakieś 40% szybciej, jednak jeszcze wolno... Ma ktoś może jakiś pomysł jak to przyśpieszyć?
Maciej G.

Maciej G. Projektant /
Programista, Famor
S.A.

Temat: Losowanie rekordów w dużej bazie danych

Cześć.

Widzę, że losujesz raczej małą liczbę rekordów (Limit 50). Czy nie możesz sobie w PHP wygenerować tablicy 50 losowo generowanych ID (mieszczących się w zakresie z bazy) i robić proste zapytanie:

SELECT tytul, domena_url FROM kategorie WHERE id IN (wylosowana_tablica).

Tablice przekazujesz jako parametr do zapytania. Wtedy wolnego RAND'a przenosisz do PHP (wygenerowanie kilkudzisięciu Integerów powinno być szybkie).

Być może są jakieś ograniczenia, których nie znam dla których takie rozwiązanie jest niemożliwe?

Pozdrawiam.Ten post został edytowany przez Autora dnia 03.10.15 o godzinie 14:15

konto usunięte

Temat: Losowanie rekordów w dużej bazie danych

losowanie na poziomie php nie jest możliwe ponieważ numery id nie są spójne, nie idą po kolei (są luki) - a ja potrzebuję zawsze 50 rekordów :(
Janusz Skudrzyk

Janusz Skudrzyk Członek zarządu,
weblabs.pl

Temat: Losowanie rekordów w dużej bazie danych

To losujesz drugi raz jak będzie brakowało.

konto usunięte

Temat: Losowanie rekordów w dużej bazie danych

a jak nie trafię 30 razy?:)
To 30 dodatkowych zapytań myślisz że nie będzie wolniejsze niż to obecne rozwiązanie?:)
Janusz Skudrzyk

Janusz Skudrzyk Członek zarządu,
weblabs.pl

Temat: Losowanie rekordów w dużej bazie danych

Sprawdź i sam zobaczysz.
Maciej G.

Maciej G. Projektant /
Programista, Famor
S.A.

Temat: Losowanie rekordów w dużej bazie danych

Łukasz P.:
a jak nie trafię 30 razy?:)
To 30 dodatkowych zapytań myślisz że nie będzie wolniejsze niż to obecne rozwiązanie?:)

Mało prawdopodobne (rachunek prawdopodobieństwa) - dość mocno zależy od układu dziur ;)

Trzeba spróbować i zobaczyć co jest szybsze ;)

BTW: można jeszcze pokombinować z podzapytaniem z kaluzulą WHERE EXISTS (Select id from kategorie). I tylko dogenerować losowe id jeśli ni mamy jeszcze 50 rekordów.

Pozdrawiam.Ten post został edytowany przez Autora dnia 03.10.15 o godzinie 16:34

konto usunięte

Temat: Losowanie rekordów w dużej bazie danych

jeśli id jest malo to wpakuj to do jakiejś kolekcji w php, I wylosuj index.
Tu masz parę fajnych rozwiązań:
https://stackoverflow.com/questions/211329/quick-select...
Marek Kubiś

Marek Kubiś programista c#

Temat: Losowanie rekordów w dużej bazie danych

Łukasz P.:
..
Baza zawiera 1.5GB i polecenie ORDER by RAND() bardzo "zamula"
(skrypt wykonuje się ponad 60 sekund). ..
OK ja użyłem takiej tabeli (baza MS SQL):

CREATE TABLE dbo.Kategorie(
KategorieID uniqueidentifier NOT NULL DEFAULT NEWID(),
Tytul nvarchar(50),
Domena_url nvarchar(100),
Enable bit Default(1),
ID int IDENTITY (1,1) NOT NULL,

CONSTRAINT PK_Kategorie PRIMARY KEY CLUSTERED (ID)
)
GO
Zrobiłem takie zapytanie:
SELECT tytul, domena_url FROM kategorie WHERE enable = 1 and RAND()<(SELECT ((50/COUNT(*))*50) FROM kategorie) ORDER BY
> RAND() LIMIT 50 ;

I skrypt działa jakieś 40% szybciej, jednak jeszcze wolno... Ma ktoś może jakiś pomysł jak to przyśpieszyć?
W podanym przykładzie jest wolno ponieważ jak sam napisałeś za każdym wywołaniem subquery jest porządkowanie pseudoprzypadkowe (ORDER BY RAND()), ale oprócz tego:
- polecenie SQL zawiera subquery co zabija jego wydajność,
- tymczasowe tabele też zabijają wydajność (użyte wcześniej) więc tutaj lepiej o nich zapomnieć,
- są indeksy ale nie są użyte na potrzeby identyfikacji rekordów (nieużywane indeksy też spowalniają),
- za każdym wywołaniem subquery jest obliczanie liczby rekordów i przeliczenie ((50/COUNT(*))*50), które jak dla mnie dewaluuje jakość funkcji losowego wyboru rekordów.

Po co tak skoro można prościej? Proponuję:
1. Z tabeli Kategorie wybrać losowo (pseudolosowo) rekord (jego ID), np funkcją:

--Funkcja zwracająca ID losowo wybranego rekordu tabeli Kategorie
ALTER FUNCTION dbo.fnRandomKategorie1 ()
RETURNS int
BEGIN
DECLARE @id int
Select @id = Max(ID)
From dbo.Kategorie
WHERE ID <= dbo.fnRand(Convert(int, 1000000)) AND Enable = 1
RETURN @id
END
GO


2. Odczytać wybraną ilość rekordów (50) tabeli Kategorie, np procedurą:

CREATE PROCEDURE spRandomKategorie
AS
BEGIN
DECLARE @counter int
BEGIN
SET @counter = 0
WHILE @counter < 50
BEGIN
SELECT Tytul, Domena_url FROM dbo.Kategorie
WHERE ID = dbo.fnRandomKategorie1()
SET @counter = @counter + 1
END
END
END
GO


Na moim komputerze wyselekcjonowanie 50 losowo wybranych rekordów spośród 100 000, powyższej procedurze zajęło czas 00:00:03. Jak dołożyłem rekordów do ponad 323K to procedura wykonywała się 00:00:04. ;-))) Mam nadzieję, że teraz czas oczekiwania nawet dla 1.5G może być już do przyjęcia. ;-)))

W załączeniu jeszcze wykorzystywana funkcjonalność pomocnicza, czyli funkcja zwracająca liczbę pseudoprzypadkową typu int z podanego zakresu:

CREATE FUNCTION fnRand(@size int)
RETURNS int
AS
BEGIN
DECLARE @returnValue int
DECLARE @rndValue DECIMAL(18,18)
SELECT @rndValue = rndResult FROM rndView
SET @returnValue = Convert(int, @size * @rndValue)
RETURN @returnValue
END
GO


A tu widok zwracający liczbę pseudoprzypadkową typu decimal, umożliwiający wykorzystanie liczb losowych w poleceniach SQL:

CREATE VIEW rndView
AS
SELECT RAND() rndResult
GO
Marcin Miga

Marcin Miga Programista. Po
prostu programista.

Temat: Losowanie rekordów w dużej bazie danych

Spróbuj z jakąś w miarę "stałą" funkcją. Np CRC32, albo MD5 i to posortuj. Aby mieć pełną losowość zrób np tak: ORDER BY CRC32(CURRENT_TIMESTAMP+id)
Marek Kubiś

Marek Kubiś programista c#

Temat: Losowanie rekordów w dużej bazie danych

Łukasz P.:
losowanie na poziomie php nie jest możliwe ponieważ numery id nie są spójne, nie idą po kolei (są luki) - a ja potrzebuję zawsze 50 rekordów :(
???

OK, ale w którym momencie one są potrzebne? A nie może być tak, że wcześniej losuje się liczby na poziomie kodu aplikacji, później sprawdza się czy rekordy istnieją, a następnie można już utworzyć zapytanie SQL, które w jednym bloku zwróci te pożądane 50 rekordów.

Ponadto można przecież wygenerować 100, 1000, .. losowych ID, zapytać o nie wszystkie ale odczytać tylko pierwsze 50 z nich, ot tak po prostu SELECT TOP 50 .. . ;-)

konto usunięte

Temat: Losowanie rekordów w dużej bazie danych

Mysql nie jest moją ulubioną bazą, ale problem jest raczej ogólny.

W google wrzuć "mysql rand alternative"... Rand pewnie jest robiony na czasie, więc dla każdego rekordu jedzie przez wszystkie warstwy do zegara systemowego. Wolne będzie - zawsze.
Limit / offset? Też będzie słabo głównie z uwagi na offset. Będzie musiał przelecieć po połowie danych - średnio.
No więc co zrobić? W sieci rozwiązania robią tak, że w WHERE wrzucone jest wyrażenie
RAND() <= wskaźnik
Wskaźnik określa prawdopodobieństwo wybrania rekordu. Chodzi o to, że system leci po wierszach i dla każdego generuje RAND - jak spełnia - bierzemy. Na koniec jest LIMIT i cześć. Zakładając, że wskaźnik jest 50% - limit * 2 - wywołań RAND. Czyli szybko. Minus jest taki, że średnio po limit * 2 rekordów... nic się nie trafi. Można na starcie sobie wylosować wiersz od którego się zaczyna, jeżeli dane są mało "losowe".

W sieci sporo rozwiązań - stack overflow jest chyba lepszym miejscem niż GL.

Następna dyskusja:

Optymalizacja bazy danych m...




Wyślij zaproszenie do