maksik
17.12.2013, 09:30:06
Witam
Mam dwie tabele powiedzmy że to są X, Y
X- znajdują się w tej tabeli artykuły do oglądania przez użytkowników, po kolei wyświetla użytkownikowi każdy rekord, gdy użytkownik obejży artykuł zapisuje się do bazy Y numer id artykułu i następnie gdy użytkownik przechodzi do następnego artykułu, wtedy skrypt sprawdza w tabeli Y który artykuł już się wyświetlił (aby nie oglądał tego samego po kolei) i pobiera z tabeli X pierwszy artykuł do wyświetlenia którego id nie jest zapisane w tabeli Y i tak wkółko.
Problem polega na tym że tabela Y się przepełnia tzn gdy uzyska kilkadziesiąt tysiecy rekordków należy ją czyścić ponieważ skrypt wyświetlania wolno zaczyna wczytywać. Jest możliwość innego rozwiązania, lub można temu jakoś zaradzić aby nie trzeba było tego czyścić?
toffiak
17.12.2013, 09:35:06
Kilkadziesiąt tysięcy rekordów to tyle co nic dla każdej bazy danych, problem leży prawdopodobie gdzie indziej.
maksik
17.12.2013, 09:38:15
a samo to, że skrypt przed wyświetleniem wyniku za każdym razem przeszukuje te ogromną tabele z id było już wyświetlane? to też nie ma wpływu?
phpion
17.12.2013, 09:38:17
Problem pewnie leży w braku/nieprawidłowych indeksach lub w zapytaniu pobierającym dane z tabeli Y. Bez struktury tabeli i zapytania nic nie poradzimy.
W sumie: pewnie możesz to oprzeć na 1 zapytaniu z wykorzystaniem NOT EXISTS.
maksik
17.12.2013, 09:58:08
samo zapytanie pobierające artykuł i sprawdzające czy nie był on już wcześniej wyświetlany, możecie określić czy powinien on być inaczej skonstruowany i to on może powodować spowalnianie przy dużej ilości rekordów?
$query = mysql_query("SELECT X.id, nazwa, wpis FROM X WHERE NOT EXISTS (Select id FROM Y WHERE X.id = Y.id and login = '".$login."') LIMIT 0,1 ");
struktura tabeli X
CREATE TABLE IF NOT EXISTS `X` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nazwa` varchar(99) CHARACTER SET latin2 DEFAULT NULL,
`limit` int(11) DEFAULT NULL,
`login` varchar(30) CHARACTER SET latin2 DEFAULT NULL,
`
date` varchar
(50
) CHARACTER SET latin2
DEFAULT NULL, `wpis` varchar(200) COLLATE utf8_polish_ci NOT NULL,
`licznik` int(11) NOT NULL,
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci AUTO_INCREMENT=42815 ;
tabeli Y
CREATE TABLE IF NOT EXISTS `Y` (
`idY` int(11) NOT NULL AUTO_INCREMENT,
`id` varchar(50) CHARACTER SET latin2 DEFAULT NULL,
`login` varchar(30) CHARACTER SET latin2 DEFAULT NULL,
`
date` varchar
(50
) CHARACTER SET latin2
DEFAULT NULL, `akcja` int(2) NOT NULL,
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_polish_ci AUTO_INCREMENT=2605241 ;
phpion
17.12.2013, 10:10:43
Cytat
WHERE X.id = Y.id and login = '".$login."'
Zmień typ kolumny Y.id na taki by odpowiadał X.id. Aktualnie masz varchar vs integer, czyli podczas przeszukiwania danych następuje rzutowanie wartości. Dodatkowo załóż
jeden indeks na obu kolumnach (id oraz login). Aktualnie masz tylko na login. Powinno znacząco pomóc.
maksik
17.12.2013, 10:21:43
czyli index dla tabeli Y powinny wyglądać tak?
PRIMARY
KEY `Indeks6`
(`idY`
),
phpion
17.12.2013, 10:28:29
Nie, napisałem Ci pogrubioną czcionką: jeden indeks na obu kolumnach. Wykonaj to:
ALTER TABLE `Y` CHANGE COLUMN `id` `id` INT(11) NOT NULL
, DROP INDEX `Indeks6`
, ADD INDEX `Indeks6` (`id` ASC, `login` ASC) ;
Sprawdź czy zapytanie przyspieszyło. Jeśli nie - wklej wyniki:
EXPLAIN SELECT ...
gdzie ... to całe Twoje zapytanie.
nospor
17.12.2013, 10:40:01
Cytat
X- znajdują się w tej tabeli artykuły do oglądania przez użytkowników, po kolei wyświetla użytkownikowi każdy rekord, gdy użytkownik obejży artykuł zapisuje się do bazy Y numer id artykułu i następnie gdy użytkownik przechodzi do następnego artykułu, wtedy skrypt sprawdza w tabeli Y który artykuł już się wyświetlił (aby nie oglądał tego samego po kolei) i pobiera z tabeli X pierwszy artykuł do wyświetlenia którego id nie jest zapisane w tabeli Y i tak wkółko.
Skoro artykuly wyswietlane są pokolei, to po co zapisywac calą sciezke jaką przeszeedl? Zapisuj tylko ostatnio obejrzany art, a nastepnie wyswietlaj art z wiekszym ID od ostatnio obejrzanego.
Ponadto w tabelach powinien byc zapisywane ID uzytkownika a nie jego login.
maksik
17.12.2013, 11:09:36
Cytat(nospor @ 17.12.2013, 10:40:01 )

Skoro artykuly wyswietlane są pokolei, to po co zapisywac calą sciezke jaką przeszeedl? Zapisuj tylko ostatnio obejrzany art, a nastepnie wyswietlaj art z wiekszym ID od ostatnio obejrzanego.
Ponadto w tabelach powinien byc zapisywane ID uzytkownika a nie jego login.
to by było dobre rozwiązanie jednak ten układ "ilościowy" jest istotny dla zliczania statystyki dniowej dla poszczególnego użytkownika
phpion
17.12.2013, 11:17:24
Dałeś "Pomógł" - rozumiem, że zapytanie przyspieszyło
maksik
17.12.2013, 11:25:45
Wpisując zapytanie ze starymi indeksami
Showing rows 0 - 0 (1 total, Wykonanie zapytania trwało 0.3033 sekund(y))
z tymi które zasugerowałeś
Showing rows 0 - 0 (1 total, Wykonanie zapytania trwało 0.4003 sekund(y))
lub
Showing rows 0 - 0 (1 total, Wykonanie zapytania trwało 0.0003 sekund(y))
Pomogło bardzo, ale nie wiem dlaczego chociaż na kilkadziesiąt prób rzadko trwa to 0.4 s , a raz ułamek sekundy!
Mam jeszcze pytanie do ciebie ponieważ kiedyś miałem problem u usługodawcy vps który notował zbyt duże obciążenia kodu php, zawsze pomagało gdy oczyścilem tabele Y czy te indeksy mogły być tego przyczyną?
phpion
17.12.2013, 11:43:25
Tak, jeżeli uruchamiałeś zapytania trwające stosunkowo długo to obciążałeś bazę danych. Im szybciej wykonywane zapytania tym mniejszy narzut. Kilkadziesiąt tysięcy rekordów to dla bazy pierdnięcie

o ile mają odpowiednie typy danych, indeksy.
CuteOne
17.12.2013, 11:48:19
Rozwiązanie to mix odpowiedzi nospora i phpiona. Stwórz tabelę archiwalną, w której będziesz jedynie dopisywał kolejne rekordy oraz tabelę taką jak zaproponował nospor. W drugiej tabeli będzie tylko jeden rekord na użytkownika co powinno znacząco pomóc w odczycie danych
maksik
19.12.2013, 22:32:04
jednak nic to nie dało... przeciążenie się pojawiło, a sposób nospora się nie sprawdzi w moim przypadku ponieważ zapytania do "większej" tabeli z bazy danych również muszą być wywoływane za każdym artykułem do liczników także chyba nie ma sensu oddzielenia tego zapytania w innej tabeli? może te zapytanie powoduje problem skoro indexowanie jest wporządku? a czy tabela X nie musi mieć również jakiś indexów?
CuteOne
20.12.2013, 08:45:11
Zamiast dodać kolumnę "licznik" do tabeli z danymi artykułu, zliczasz ilość wejść COUNT'em?
maksik
20.12.2013, 09:32:26
dokładnie tak, COUNT może wywoływać nadmierne obciążenia?
nospor
20.12.2013, 09:39:20
No ale po co ci tu count?? W mojej wersji miales zapisywac tylko ID ostatnio ogladanego rekordu dla danego usera i tu zaden count nie jest potrzebny.
W mixie zas, gdzie miales dodac tabele archiwalną to tam jest tez tylko INSERT wiec COUNT nadal nigdzie nie jest potrzebny.
W mixie masz lepsza sytuacje, bo do duzej tabeli idziesz tylko raz wraz z insertem a nie jak teraz dwa razy do odczytu ostatniego id i do inserta.
Zas co do indeksu w X to chyba juz nie trzeba, bo do arta przeciez odwolujesz sie przez ID a ono jest kluczem glownym, czyz nie?
maksik
20.12.2013, 09:46:02
tak COUNT używam do zliczania artykułów obejżanych przez użytkownika i następnie do ustawień statystyki dla każdego z użytkownika która jest wyświetlana zawsze po każdym obejżeniu dlatego jeżeli powoduje przeciążenie to w tabeli archiwalnej chyba te obciążenie będzie dalej występować?
nospor
20.12.2013, 09:51:18
Ciezko cos ogarnac z tych opisow twoich....
COUNT samo w sobie jest bardzo szybkie nawet dla bardzo duzej liczby rekordow.
1) Sprawdz czas wszystkich zapytan jakie generujesz, to bedziesz wiedzial ktore dokladnie ci muli. Tu nie ma co zgadywac.
2) Powinienes napisac poprawnie swoją tabele. Pisalem ci juz o tym wczesniej, przypomne jeszcze raz:
`login` varchar(30) CHARACTER SET latin2 DEFAULT NULL,
`date` varchar(50) CHARACTER SET latin2 DEFAULT NULL,
`akcja` int(2) NOT NULL,
Nie powinno byc pola login, tylko id usera
Data powinna być polem datowym a nie tekstem. Wkoncu data to data.
akcja powinna byc tinyint a nie int, bo zakladam ze tam są male liczby.
To tak na poczatek.
maksik
28.12.2013, 12:00:58
Utworzyłem drugą tabele według zaleceń co prawda przy tej samej ilości rekordów waży ona 2x mniej, ale czas zapytania tej bazy trwa 5x dłużej, może zapytanie powinno wyglądać inaczej? zmieniło się w zapytaniu to, że wyszukuje rekordy użytkownika nie po loginie, a jego numerze id
sazian
28.12.2013, 12:45:16
pokaż wynik explaina
czyli explain select ....
maksik
28.12.2013, 12:56:03
dla zapytania explain zwraca mi taki wynik
dla starej tabeli Y
id select_type table type possible_keys
key key_len ref rows Extra
1 PRIMARY X ALL NULL NULL NULL NULL 6059 Using where
2 DEPENDENT SUBQUERY Y ref Indeks6 Indeks6 33 const 1 Using where
dla nowej tabeli Y
id select_type table type possible_keys
key key_len ref rows Extra
1 PRIMARY X ALL NULL NULL NULL NULL 6059 Using where
2 DEPENDENT SUBQUERY Y2 ref Indeks6 Indeks6 4 const 3 Using where
sazian
28.12.2013, 15:46:32
sprawdź czy dla nowej tabeli Y nie masz zduplikowanych danych, dlatego że zostały pobrane 3 rekordy, a w starej wersji tylko jeden
po drugie NOT EXISTS nie wykorzystuje indeksów co sprawia że całe zapytanie jest wolniejsze, spróbuj je zastąpić przez NOT IN x.ID(SELECT ...).
Zauważ że z tabeli X zostało pobrane 6059 rekordów,
możesz spróbować ograniczyć tą ilość. Skoro szukany następny wpis to można założyć że ma on większe ID, więc możesz dodać WHERE x.ID>obocne_id AND x.ID NOT IN(...)
po dokonaniu zmian zobacz wynik EXPLAIN czy coś się poprawiło
maksik
30.12.2013, 10:22:57
czy zamiast tego zapytania
$query = mysql_query("SELECT X.id, nazwa, wpis FROM X WHERE NOT EXISTS (Select id FROM Y WHERE X.id = Y.id and login = '".$login."') LIMIT 0,1 ");
zastąpić je na takie które wyszukuje id ostatniego artykułu, a następnie wyświetla następny o większym numerze od poprzedniego?
Czy to drugie rozwiązanie jest bardziej optymalne?
$query = mysql_query("SELECT id FROM Y WHERE login = '".$login."' ORDER BY id DESC LIMIT 0,1"); $numerid = $query['id'];
$query = mysql_query("SELECT X.id, X.nazwa, X.wpis FROM X WHERE id > '".$numerid."' ORDER BY id LIMIT 0,1");
sazian
30.12.2013, 20:33:00
spróbuj np. tak
SELECT X.id, nazwa, wpis FROM X WHERE X.ID>'".$numerid."' AND X.ID> (SELECT max(Y.id) FROM Y WHERE Y.ID>'".$numerid."' AND login = '".$login."') LIMIT 0,1
pamiętaj aby zawsze sprawdzać przez explain co jest bardziej wydajne
maksik
30.12.2013, 20:56:48
Nie mogę chyba tego zastosować w jednym zapytaniu ponieważ parametr $numerid znajduję za pomocą pierwszego zapytania.
Na które parametry w wynikach explain muszę zwracać szczególną uwagę?
sazian
30.12.2013, 22:17:59
wszystkie

przykładowo zobacz na twoje ostatnie wyniki
w pierwszym w kolumnie rows miałeś 6000 z jeden tabeli i 1 z drugiej czyli baza musiała "ręcznie" porównać 6000
w drugim przypadku miałeś 6000 i 3 czyli trzeba było ręcznie porównać 6000*3 rekordów
dalej w type masz "all" co oznacza że nie udało się skorzystać z żadnego indeksu i baza musiała analizować wszystkie rekordy w bazie.
Skoro nie masz nigdzie zapisanego id ostatniego artykułu to pomiń ten warunek(X.ID>'".$numerid."') w takim przypadku niema sensu dwa razy pobierać prawie tego samego
ogólnie polecam poczytać tutaj
http://blog.ksiazek.info/2010/04/09/benchmark-i-profiling/ oczywiście nie tylko ten wpis
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.