Het optimaliseren van query's voor de volgende en vorige element

stemmen
28

Ik ben op zoek naar de beste manier om de volgende en vorige records van een record op te halen zonder het uitvoeren van een volledige query. Ik heb een volledig geïmplementeerde oplossing op zijn plaats, en zou graag willen weten of er betere manieren om dit er uit te doen.

Laten we zeggen dat we het bouwen van een website voor een fictieve groenteboer. Naast zijn HTML-pagina's, elke week, wil hij een lijst van speciale aanbiedingen op zijn site te publiceren. Hij wil deze aanbiedingen in een echte database tabel om te verblijven, en de gebruikers moeten in staat zijn om de aanbiedingen te sorteren op drie manieren.

Elk item heeft ook een detail pagina te hebben met meer, tekstuele informatie over het aanbod en de vorige en volgende knoppen. De vorige en volgende knoppen moeten wijzen op de naburige ingangen afhankelijk van het sorteren van de gebruiker voor de lijst had gekozen .

alt-tekst http://www.pekkagaiser.com/stuff/Sort.gif?

Uiteraard is de knop volgende voor Tomaten, klasse I moet zijn Appelen, klasse 1 in het eerste voorbeeld, Peren, klasse I in de tweede, en niemand in de derde.

De taak in de detailweergave is naar de volgende en vorige items te bepalen zonder dat een query elke keer , met de sorteervolgorde van de lijst als de enige beschikbare informatie (Laten we zeggen dat we dat door middel van een GET parameter ?sort=offeroftheweek_price, en de gevolgen voor de veiligheid te negeren) .

Het is duidelijk, eenvoudig passeren van de ID's van de volgende en vorige elementen als parameter is de eerste oplossing die bij me opkomt. Immers, we kennen de ID's op dit punt. Maar, dit is geen optie hier - het zou werken in dit vereenvoudigd voorbeeld, maar niet in veel van mijn echte wereld use cases.

Mijn huidige aanpak in mijn CMS is het gebruik van iets wat ik heb de naam sorteren cache. Wanneer er een lijst wordt geladen, ik bewaar het punt posities in records in een tabel met de naam sortingcache.

name (VARCHAR)             items (TEXT)

offeroftheweek_unsorted    Lettuce; Tomatoes; Apples I; Apples II; Pears
offeroftheweek_price       Tomatoes;Pears;Apples I; Apples II; Lettuce
offeroftheweek_class_asc   Apples II;Lettuce;Apples;Pears;Tomatoes

natuurlijk, het itemsis kolom echt bevolkt met numerieke ID's.

In de detailpagina, ik toegang tot nu de juiste sortingcacherecord, haal de itemskolom, exploderen het, op zoek naar de huidige item ID, en de terugkeer van de vorige en volgende buurman.

array(current   => Tomatoes,
      next      => Pears,
      previous  => null
      );

Dit is natuurlijk duur, werkt voor een beperkt aantal registreert alleen en creëert redundante data, maar laten we aannemen dat in de echte wereld, de query om de lijsten te maken is erg duur (het is), het runnen van het in elk detail uitzicht is uit de vraag, en sommige caching nodig is.

Mijn vragen:

  • Denk je dat dit is een goede gewoonte om erachter te komen de naburige records voor wisselende vraag orders?

  • Kent u betere praktijken in termen van prestaties en eenvoud? Heeft u iets dat dit volkomen overbodig maakt weten?

  • In de programmering theorie, is er een naam voor dit probleem?

  • Is de naam Sorting cache passend en begrijpelijk zijn voor deze techniek?

  • Zijn er erkend, gemeenschappelijke patronen om dit probleem op te lossen? Hoe worden ze genoemd?

Opmerking: Mijn vraag is niet over het bouwen van de lijst, of hoe je de details weer te geven. Dat zijn slechts voorbeelden. Mijn vraag is de basisfunctionaliteit van het bepalen van de buren van een record bij een re-query is onmogelijk, en de snelste en goedkoopste manier om er te komen.

Als er iets niet duidelijk is, kunt u een bericht en ik zal verduidelijken.

Het starten van een bounty - misschien is er wat meer informatie over deze die er zijn.

De vraag is gesteld op 22/02/2010 om 12:06
bron van user
In andere talen...                            


11 antwoorden

stemmen
-3

Dus je hebt twee taken:

  1. bouwen gesorteerde lijst van items (selecteert met verschillende ORDER BY)
  2. toon details over elk item (SELECT gegevens uit de database met mogelijke caching).

Wat is het probleem?

PS: als geordende lijst te groot kan je gewoon PAGER functionaliteit geïmplementeerd nodig. Er kunnen verschillende implementaties, zoals u kunt wensen om "LIMIT 5" toe te voegen in vraag en zorgen voor "Toon komende 5" knop. Wanneer deze knop wordt ingedrukt, staat als "waar de prijs <0,89 LIMIT 5" wordt toegevoegd.

antwoordde op 22/02/2010 om 15:04
bron van user

stemmen
16

Hier is een idee. Je kon de dure operaties om een ​​update te ontlasten wanneer de kruidenier inserts / updates nieuwe aanbiedingen in plaats van wanneer de eindgebruiker kiest de gegevens te bekijken. Dit lijkt misschien een niet-dynamische manier om het soort gegevens te verwerken, maar het kan de snelheid te verhogen. En, zoals we weten, is er altijd een afweging tussen prestaties en andere codering factoren.

Maak een tabel volgende en vorige voor elk aanbod en elke soort optie om vast te houden. (Als alternatief, kunt u deze in de aanbieding tabel op te slaan als je altijd zal bestaan ​​uit drie sorteeropties - vraag snelheid is een goede reden om uw database denormalize)

Dus je zou deze kolommen hebben:

  • Sorteren Type (Ongesorteerd, Prijs, Class en Price Desc)
  • aanbod ID
  • Vorige ID
  • Volgende ID

Wanneer de detail informatie voor de aanbieding detail pagina wordt opgevraagd uit de database, zou het NextID en PrevID een deel van de resultaten. Dus zou je maar één vraag voor elk detail pagina nodig.

Elke keer dat een bod wordt geplaatst, bijgewerkt of verwijderd, zou je nodig hebt om een ​​proces dat de integriteit / juistheid van de sorttype tafel valideert draaien.

antwoordde op 22/02/2010 om 20:20
bron van user

stemmen
1

Ik weet niet zeker of ik het goed begrepen, dus als niet, vertel me;)

Laten we zeggen, dat de givens zijn de query voor de gesorteerde lijst en de huidige verschuiving in die lijst, dus hebben we een $queryen een $n.

Een zeer voor de hand liggende oplossing om de query's te minimaliseren, zou zijn om alle gegevens op te halen in een keer:

list($prev, $current, $next) = DB::q($query . ' LIMIT ?i, 3', $n - 1)->fetchAll(PDO::FETCH_NUM);

Deze verklaring haalt de vorige, de huidige en de volgende elementen uit de database in de huidige sorteervolgorde en legt de bijbehorende informatie in de bijbehorende variabelen.

Maar deze oplossing is te simpel, ik neem aan dat ik iets verkeerd begrepen.

antwoordde op 07/02/2011 om 20:31
bron van user

stemmen
2

Ik heb nachtmerries met deze zo goed had. Uw huidige aanpak lijkt de beste oplossing, zelfs voor lijsten van 10k items. Caching de ID's van de lijstweergave in de http-sessie en vervolgens met behulp van dat voor het weergeven van de (gepersonaliseerde naar huidige gebruiker) vorige / volgende. Dit werkt goed vooral wanneer er te veel manieren om te filteren en sorteren van de eerste lijst van items in plaats van slechts 3.
Ook door het opslaan van de hele lijst met ID je bij een weer te geven "you are at X out of Y"usability verbeteren van tekst.
JIRA vorige / volgende

By the way, dit is wat JIRA doet ook.

Rechtstreeks te antwoorden op uw vragen:

  • Ja, het is een goede gewoonte, omdat het schalen zonder enige toegevoegde complexiteit van de code wanneer uw filter / sorteren en itemtypen kraaien complexer. Ik gebruik het in een productiesysteem met 250k artikelen met "oneindig" filter / sort variaties. Trimmen de cacheable IDs tot 1000 is ook een mogelijkheid, omdat de gebruiker zal zeer waarschijnlijk nooit op vorige of volgende meer dan 500 keer (Hij zal waarschijnlijk terug te gaan en de zoekopdracht te verfijnen of paginering).
  • Ik weet niet van een betere manier. Maar als de soorten, waar beperkt en dit was een openbare site (zonder http sessie) dan zou ik waarschijnlijk denormalize.
  • Ik weet niet.
  • Ja, het sorteren cache klinkt goed. In mijn project noem ik het "vorige / volgende op zoekresultaten" of "navigatie op zoekresultaten".
  • Ik weet niet.
antwoordde op 07/02/2011 om 21:04
bron van user

stemmen
2

Over het algemeen ben denormaliseren de gegevens van de indexen. Ze kunnen worden opgeslagen in dezelfde rijen, maar ik bijna altijd op te halen mijn resultaat IDs, maak dan een aparte reis voor de gegevens. Dit maakt caching de gegevens zeer eenvoudig. Het is niet zo belangrijk in PHP, waar de latentie is laag en de bandbreedte hoog, maar een dergelijke strategie is erg handig als u een hoge latency, lage bandbreedte toepassing, zoals een AJAX website waar een groot deel van de site is gemaakt in JavaScript.

Ik cachen altijd de lijsten van de resultaten en de resultaten zelf apart. Als er iets van invloed op de resultaten van een lijst query, de cache van de lijst met resultaten wordt vernieuwd. Als er iets zelf invloed op de resultaten, die bijzondere resultaten zijn vernieuwd. Dit stelt me ​​in staat om ofwel één bij te werken zonder dat alles te regenereren, wat resulteert in effectieve caching.

Sinds mijn lijsten van de resultaten zelden veranderen, genereer ik alle lijsten tegelijk. Dit kan de eerste reactie iets langzamer te maken, maar het vereenvoudigt cache verfrissende (alle lijsten krijgen opgeslagen in een enkele cache entry).

Omdat ik de hele lijst in de cache, het is triviaal om items in de omgeving te vinden zonder revisie van de database. Met een beetje geluk zullen de gegevens voor die items ook worden gecached. Dit is vooral handig bij het sorteren van data in JavaScript. Als ik een kopie in de cache op de client al hebben, kan ik meteen toevlucht nemen.

Om uw specifieke vragen te beantwoorden:

  • Ja, het is een fantastisch idee van te voren uit te vinden de buren, of wat dan ook de gegevens van de klant is het waarschijnlijk om toegang te krijgen volgende, vooral als de kosten is nu laag is en de kosten voor opnieuw te berekenen is hoog. Dan is het gewoon een afweging van extra pre-berekening en opslag versus snelheid.
  • In termen van prestaties en eenvoud, vermijd koppelverkoop samen dingen die logisch zijn verschillende dingen. Indexen en gegevens verschillend zijn waarschijnlijk worden gewijzigd op verschillende tijdstippen (bijvoorbeeld het toevoegen van een nieuwe referentiepunt invloed op de indexen, maar de bestaande gegevens), en moeten derhalve apart worden bekeken. Dit kan iets minder efficiënt zijn vanuit een single-threaded oogpunt, maar elke keer dat je samen iets te binden, verlies je caching effectiviteit en asychronosity (de sleutel tot schaalvergroting is asychronosity).
  • De term voor het verkrijgen van data van tevoren is vooraf ophalen. -Prefetching kan gebeuren op het moment van toegang of op de achtergrond, maar voordat de pre-opgehaalde data daadwerkelijk nodig is. Eveneens met pre-berekening. Het is een trade-off van de kosten nu, opslag kosten, en de kosten indien nodig te krijgen.
  • "Sorting cache" is een passende naam.
  • Ik weet het niet.

Ook, als je dingen in de cache, cache ze op de meest generieke niveau mogelijk te maken. Sommige dingen kunnen gebruikersspecifieke (zoals resultaten voor een zoekopdracht), waar anderen gebruiker agnostisch zou kunnen zijn, zoals surfen op een catalogus zijn. Beide kunnen profiteren van caching. De catalogus vraag zou kunnen worden frequent en op te slaan een beetje elke keer, en de zoekopdracht kan duur zijn en bespaart een hoop een paar keer.

antwoordde op 09/02/2011 om 08:00
bron van user

stemmen
0

Er zijn zo veel manieren om dit te doen met betrekking tot de huid van de spreekwoordelijke kat. Dus hier zijn een paar van mij.

Als uw oorspronkelijke query is duur, wat je zegt het is, maak vervolgens een andere tafel misschien een geheugen tafel bevolken het met de resultaten van uw dure en zelden lopen hoofdquery.

Deze tweede tabel kan dan worden opgevraagd op elke view en het sorteren is zo simpel als het instellen van de juiste sorteervolgorde.

Zoals vereist herbevolken de tweede tabel met de resultaten van de eerste tabel, waardoor het houden van de gegevens fris, maar het minimaliseren van het gebruik van de dure query.

Als alternatief, als je wilt zelfs te voorkomen dat de verbinding met de db dan kun je alle gegevens in een php-array op te slaan en op te slaan met behulp van memcached. dit zou zeer snel zijn en op voorwaarde dat uw lijsten waren niet al te groot zou worden resources efficiënt. en kan gemakkelijk worden opgelost.

DC

antwoordde op 11/02/2011 om 05:19
bron van user

stemmen
0

Uitgangspunten:

  • Specials zijn wekelijkse
  • We kunnen verwachten dat de site zelden ... waarschijnlijk dagelijks veranderen?
  • We kunnen updates controleren om de database met ether een API of te reageren via triggers

Als de site verandert op een dagelijkse basis, stel ik voor dat alle pagina's statisch 's nachts worden gegenereerd. Een query voor elke sort-order doorloopt en maakt alle gerelateerde pagina's. Zelfs als er dynamische elementen, is de kans groot dat je ze kunt pakken door met inbegrip van de statische pagina-elementen. Dit zou een optimale pagina service en geen belasting voor de database te bieden. In feite zou je misschien het genereren van afzonderlijke pagina's en vorige / volgende elementen die zijn opgenomen in de pagina's. Dit kan gekker met 200 manieren om te sorteren, maar met 3 Ik ben een grote fan van.

?sort=price
include(/sorts/$sort/tomatoes_class_1)
/*tomatoes_class_1 is probably a numeric id; sanitize your sort key... use numerics?*/

Als om wat voor reden is dit niet haalbaar is, zou ik toevlucht tot memoriseren. Memcache is populair voor dit soort dingen (woordspeling!). Wanneer er iets wordt geduwd om de database, kunt u een trigger geven om uw cache te werken met de juiste waarden. Doe dit op dezelfde manier als u zou doen als alsof uw bijgewerkte punt bestond in 3 gelinkte lijsten - relinken zo nodig (this.next.prev = this.prev, etc). Vanaf dat, zolang uw cache niet te vol, zult u in te trekken eenvoudige waarden uit het geheugen in een primaire sleutel mode.

Deze methode zal een aantal extra codering op de select en bijwerken / insert methoden te nemen, maar het moet vrij minimaal zijn. Op het einde, zult u opzoeken [id of tomatoes class 1].price.next. Als die sleutel is in de cache, gouden. Zo niet, dan invoegen in cache en display.

  • Denk je dat dit is een goede gewoonte om erachter te komen de naburige records voor wisselende vraag orders? Ja. Het is verstandig om look-aheads uitvoeren van de verwachte toekomstige verzoeken.
  • Kent u betere praktijken in termen van prestaties en eenvoud? Heeft u iets dat dit volkomen overbodig maakt weten? Hopelijk bovenstaande
  • In de programmering theorie, is er een naam voor dit probleem? Optimalisatie?
  • Is de naam "Sorting cache" passend en begrijpelijk zijn voor deze techniek? Ik ben niet zeker van een specifiek toepasselijke naam. Het is caching, het is een cache van soorten, maar ik ben er niet zeker van dat me te vertellen heb je een "sorteren cache" zou direct inzicht over te brengen.
  • Zijn er erkend, gemeenschappelijke patronen om dit probleem op te lossen? Hoe worden ze genoemd? Caching?

Sorry mijn tailing antwoorden zijn soort nutteloos, maar ik denk dat mijn verhaal oplossingen moet heel nuttig zijn.

antwoordde op 11/02/2011 om 18:13
bron van user

stemmen
0

Je kon de bespaart rijnummers van de geordende lijsten in uitzicht , en je kon de vorige en volgende items in de lijst onder (current_rownum-1) en (current_rownum + 1) rijnummers bereiken.

antwoordde op 12/02/2011 om 14:01
bron van user

stemmen
0

Het probleem / datastructur is vernoemd bi-directionele grafiek of je zou kunnen zeggen dat je hebt een aantal gelinkte lijsten.

Als je denkt dat het als een gekoppelde lijst, kon je gewoon velden toe te voegen aan de items tafel voor iedere sorteer- en vorige / volgende toets. Maar de DB Persoon zal je vermoorden voor dat, het is net als GOTO.

Als je denkt dat het als een (bi-) directioneel grafiek, ga je met het antwoord van Jessica's. Het grootste probleem is dat er die volgorde updates zijn dure operaties.

 Item Next Prev
   A   B     -
   B   C     A
   C   D     B
   ...

Als je één items positie veranderen om de nieuwe orde A, C, B, D, moet u 4 rijen bij te werken.

antwoordde op 13/02/2011 om 02:20
bron van user

stemmen
4

Ik heb een idee enigszins vergelijkbaar met Jessica's. Echter, in plaats van het opslaan van links naar de volgende en vorige ingedeeld, de sorteervolgorde voor elk type soort op te slaan je. Om de vorige of volgende record te vinden, gewoon de rij met Sortx = currentSort ++ of Sortx = currentSort--.

Voorbeeld:

Type     Class Price Sort1  Sort2 Sort3
Lettuce  2     0.89  0      4     0
Tomatoes 1     1.50  1      0     4
Apples   1     1.10  2      2     2
Apples   2     0.95  3      3     1
Pears    1     1.25  4      1     3

Deze oplossing zou zeer korte vraag tijden geven, en zou nemen minder schijfruimte in beslag dan Jessica's idee. Echter, zoals ik weet zeker dat je je realiseert, de kosten van het bijwerken van een rij van de gegevens is aanzienlijk hoger, omdat je opnieuw te berekenen en op te slaan allerlei orders. Maar toch, afhankelijk van uw situatie, als de gegevens updates zijn zeldzaam en vooral als ze altijd gebeuren in bulk, dan zou deze oplossing de beste zijn.

d.w.z

once_per_day
  add/delete/update all records
  recalculate sort orders

Hoop dat dit nuttig is.

antwoordde op 13/02/2011 om 03:30
bron van user

stemmen
0

Excuses als ik het verkeerd begrepen hebben, maar ik denk dat je de geordende lijst te behouden tussen de gebruiker toegang tot de server. Als dat zo is, kan uw antwoord goed liggen in uw caching strategie en technologieën in plaats van in database query / schema optimalisatie.

Mijn aanpak zou zijn om serialize () de array eenmaal zijn eerste teruggehaald, en vervolgens de cache die in een aparte berging; of dat nu memcached / APC / hard-drive / MongoDB / etc. en behoudt zijn cache locatiegegevens voor elke gebruiker individueel via hun sessie data. De feitelijke opslag backend zou natuurlijk afhankelijk van de grootte van de array, die je niet in veel details over te gaan, maar memcached schalen grote over meerdere servers en mongo nog verder op een iets grotere latency kosten.

Je hoeft ook niet aangeven hoeveel soort permutaties zijn er in de echte wereld; bijvoorbeeld heb je nodig om afzonderlijke lijsten per gebruiker in de cache, of kunt u globaal cache per soort permutatie en filtert dan wat je niet nodig hebt via PHP ?. In het voorbeeld dat je geeft, zou ik gewoon cachen zowel permutaties en op te slaan welke van de twee die ik nodig had () om unserialize in de sessie data.

Wanneer de gebruiker terugkeert naar de site, controleert u de tijd om de waarde van de gegevens in het cachegeheugen Leef en opnieuw te gebruiken indien nog geldig is. Ik zou ook een trigger die op INSERT IGNORE / UPDATE / DELETE voor de speciale aanbiedingen die een tijdstempel veld gewoon sets in een aparte tabel. Dit zou meteen aangeven of de cache was muf en de query die nodig is om opnieuw uit te voeren voor een zeer lage vraag kosten. Het mooie van alleen met behulp van de trekker aan één veld ingesteld is dat er geen zorgen te maken over het snoeien van oude / redundant waarden uit die tabel.

Of dit is geschikt zou afhangen van de grootte van de gegevens die worden teruggestuurd, hoe vaak het bewerkt is, en wat caching technologieën die beschikbaar zijn op uw server.

antwoordde op 13/02/2011 om 15:47
bron van user

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more