Ottimizzazione delle query per l'elemento successivo e precedente

voti
28

Sto cercando il modo migliore per recuperare i record precedente e successiva di un record senza eseguire una query full. Ho una soluzione pienamente attuato in atto, e vorrei sapere se ci sono approcci di meglio per farlo là fuori.

Diciamo che stiamo costruendo un sito web per un fruttivendolo fittizia. In aggiunta alle sue pagine HTML, ogni settimana, vuole pubblicare un elenco delle offerte speciali sul suo sito. Vuole queste offerte di risiedere in una tabella di database vero e proprio, e gli utenti devono essere in grado di selezionare le offerte in tre modi.

Ogni articolo deve avere anche una pagina di dettaglio con ulteriori, informazioni testuali sull'offerta e pulsanti precedenti e Avanti. Il precedente e pulsanti Avanti devono puntare alle voci vicine a seconda della selezione che l'utente aveva scelto per la lista .

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

Ovviamente, il pulsante next per Pomodori, classe I deve essere Mele, classe 1 nel primo esempio, Pere, classe I nel secondo, e nessuno nel terzo.

Il compito nella visualizzazione dettagliata è di determinare le voci successive e precedenti senza esecuzione di una query ogni volta , con l'ordinamento della lista come le uniche informazioni disponibili (Diciamo otteniamo che, attraverso un parametro GET ?sort=offeroftheweek_price, e ignorare le implicazioni di sicurezza) .

Ovviamente, semplicemente passando gli ID degli elementi precedente e successivo come parametro è la prima soluzione che viene in mente. Dopo tutto, abbiamo già conoscere l'ID di a questo punto. Ma, questo non è un'opzione qui - che avrebbe funzionato in questo esempio semplificato, ma non in molti dei miei casi d'uso reali.

Il mio attuale approccio nel mio CMS sta usando qualcosa che ho chiamato l'ordinamento cache. Quando un elenco viene caricato, devo conservare le posizioni degli oggetti in record in una tabella di nome 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

ovviamente, la itemscolonna è davvero popolata con ID numerici.

Nella pagina dei dettagli, io ora accedo al appropriata sortingcachecronaca, prendere la itemscolonna, esplodere, cercare l'ID elemento corrente, e restituire il vicino precedente e successivo.

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

Questo è ovviamente costoso, lavora per un numero limitato di soli record e crea i dati ridondanti, ma supponiamo che nel mondo reale, la query per creare le liste è molto costoso (è), in esecuzione in ogni dettaglio è fuori la questione, e un po ' è necessaria la memorizzazione nella cache.

Le mie domande:

  • Pensi che questa è una buona pratica per scoprire i record vicini per diversi ordini di query?

  • Sai le pratiche migliori in termini di prestazioni e semplicità? Sai una cosa che rende questo del tutto obsoleto?

  • In teoria la programmazione, c'è un nome per questo problema?

  • È il nome di cache Ordinamento è appropriato e comprensibile per questa tecnica?

  • Ci sono riconosciuti, modelli comuni per risolvere questo problema? Come si chiamano?

Nota: La mia domanda non è di costruire la lista, o il modo di visualizzare la vista di dettaglio. Questi sono solo esempi. La mia domanda è la funzionalità di base di determinare i vicini di un record quando un re-query è impossibile, e il modo più veloce e più economico per arrivarci.

Se qualcosa non è chiaro, si prega di lasciare un commento e io chiarire.

Avvio di una taglia - forse c'è un po 'di informazioni là fuori.

È pubblicato 22/02/2010 alle 12:06
fonte dall'utente
In altre lingue...                            


11 risposte

voti
-3

In modo da avere due compiti:

  1. costruire elenco ordinato di oggetti (SELECT con diversa ORDER BY)
  2. mostrare i dettagli su ciascuna voce (selezionare Dettagli dal database con possibilità di caching).

Qual è il problema?

PS: se elenco ordinato potrebbe essere troppo grande basta funzionalità implementate PAGER. Ci potrebbero essere diverse implementazioni, ad esempio, si potrebbe desiderare di aggiungere "LIMIT 5" in query e fornire "Mostra prossimo 5" pulsante. Quando si preme questo tasto, condizione come "WHERE prezzo <0.89 LIMIT 5" viene aggiunto.

Risposto il 22/02/2010 a 15:04
fonte dall'utente

voti
16

Ecco un'idea. Si potrebbe scaricare le operazioni costose un aggiornamento quando gli inserti droghiere / aggiorna le nuove offerte, piuttosto che quando l'utente finale seleziona i dati da visualizzare. Questo può sembrare un modo non dinamico per gestire i dati di ordinamento, ma può aumentare la velocità. E, come sappiamo, c'è sempre un compromesso tra prestazioni e di altri fattori di codifica.

Creare una tabella per contenere successivo e precedente per ogni offerta e ogni opzione di ordinamento. (In alternativa, è possibile memorizzare questo nella tabella offerta se si avrà sempre tre opzioni di ordinamento - velocità di query è un buon motivo per denormalizzare vostra base di dati)

Così si avrebbe queste colonne:

  • Tipo di ordinamento (Unsorted, Prezzo, classe e prezzo Asc)
  • ID offerta
  • prev ID
  • Avanti ID

Quando le informazioni di dettaglio per la pagina delle offerte dettaglio è interrogato dal database, il NextID e Previd sarebbero parte dei risultati. Così si avrebbe solo bisogno di una query per ogni pagina di dettaglio.

Ogni volta che viene inserita un'offerta, aggiornato o cancellato, si avrebbe bisogno di eseguire un processo che convalida l'integrità / accuratezza della tabella sorttype.

Risposto il 22/02/2010 a 20:20
fonte dall'utente

voti
1

Io non sono sicuro se ho capito bene, quindi se non, mi dica;)

Diciamo, che i dati di fatto sono la query per la lista ordinata e la corrente di offset in tale elenco, cioè abbiamo una $queryed una $n.

Una soluzione molto evidente per ridurre al minimo le query, potrebbe essere quella di recuperare tutti i dati in una sola volta:

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

Tale affermazione recupera il precedente, l'attuale e le prossime elementi dal database nell'ordine di ordinamento attuale e mette le informazioni associate nelle variabili corrispondenti.

Ma in quanto questa soluzione è troppo semplice, suppongo ho frainteso qualcosa.

Risposto il 07/02/2011 a 20:31
fonte dall'utente

voti
2

Ho avuto incubi con questo uno pure. Il tuo attuale approccio sembra essere la soluzione migliore anche per gli elenchi di 10k elementi. Caching dei ID di visualizzazione elenco nella sessione HTTP e quindi utilizzando quella per la visualizzazione del (personalizzato per l'utente corrente) precedente / successiva. Questo funziona bene soprattutto quando ci sono troppi modi per filtrare e ordinare l'elenco iniziale di oggetti invece di solo 3.
Inoltre, memorizzando l'intera lista ID si arriva a visualizzare un "you are at X out of Y"testo di usabilità migliorando.
di JIRA precedente / successiva

Tra l'altro, questo è ciò che JIRA fa pure.

Per rispondere direttamente alle vostre domande:

  • Sì, è buona pratica, perché le scale senza alcuna complessità codice aggiunto quando il filtro / ordinamento e tipi di elemento corvo più complessa. Lo sto usando in un sistema di produzione con 250k articoli con varianti filtro / ordinamento "infinite". Taglio gli ID memorizzabili nella cache al 1000 è anche una possibilità in quanto l'utente sarà molto probabilmente mai cliccare su prev o successivo più di 500 volte (Egli sarà molto probabilmente tornare indietro e raffinare la ricerca o impaginare).
  • Non conosco un modo migliore. Ma se il genere dove limitato e questo era un luogo pubblico (con nessuna sessione HTTP), allora mi piacerebbe molto probabilmente denormalizzare.
  • Boh.
  • Sì, l'ordinamento della cache suona bene. Nel mio progetto Io lo chiamo "precedente / successiva nei risultati di ricerca" o "la navigazione nei risultati di ricerca".
  • Boh.
Risposto il 07/02/2011 a 21:04
fonte dall'utente

voti
2

In generale, ho denormalizzare i dati dagli indici. Essi possono essere conservati nelle stesse righe, ma ho quasi sempre recuperare il mio ID di risultato, poi fare un viaggio a parte per i dati. Questo rende il caching dei dati molto semplice. Non è così importante in PHP, dove la latenza è bassa e la larghezza di banda elevata, ma questa strategia è molto utile quando si ha un elevata latenza, applicazioni a bassa larghezza di banda, come ad esempio un sito web AJAX in cui gran parte del sito è resa in JavaScript.

Ho sempre Caché gli elenchi dei risultati, e gli stessi risultati separatamente. Se qualcosa colpisce i risultati di una query lista, la cache dei risultati della lista viene aggiornata. Se qualcosa colpisce i risultati stessi, questi risultati particolari vengono aggiornate. Questo mi permette di aggiornare uno dei due, senza dover rigenerare tutto, con conseguente caching efficace.

Dal momento che le mie liste dei risultati cambiano raramente, ho generare tutte le liste allo stesso tempo. Questo può rendere la risposta iniziale leggermente più lento, ma semplifica la cache rinfrescante (tutte le liste vengono memorizzati in una singola voce di cache).

Perché ho la lista intera cache, è banale per trovare gli oggetti vicini senza rivedere il database. Con la fortuna, i dati relativi a tali elementi saranno memorizzate nella cache. Ciò è particolarmente utile quando l'ordinamento dei dati in JavaScript. Se ho già una copia memorizzata nella cache sul client, posso ricorrere immediatamente.

Per rispondere alle vostre domande in particolare:

  • Sì, è una fantastica idea per scoprire i vicini prima del tempo, o altre eventuali informazioni il cliente rischia di accedere prossimo, soprattutto se il costo è basso ora e il costo di ricalcolare è alto. Poi è semplicemente un compromesso di più pre-calcolo e di storage in funzione della velocità.
  • In termini di prestazioni e di semplicità, evitare le cose legare insieme che sono logicamente cose diverse. Indici e dati sono diversi, sono suscettibili di essere cambiato in tempi diversi (ad esempio l'aggiunta di un nuovo dato influenzerà gli indici, ma non i dati esistenti), e quindi devono essere accessibili separatamente. Questo può essere un po 'meno efficiente dal punto di vista single-threaded, ma ogni volta che si legano qualcosa insieme, si perde il caching efficacia e asychronosity (la chiave per la scala è asychronosity).
  • Il termine per ottenere i dati prima del tempo è pre-fetching. Pre-fetching può accadere al momento dell'accesso o in background, ma prima che i dati pre-inverosimile è effettivamente necessario. Allo stesso modo con pre-calcolo. Si tratta di un trade-off tra il costo ora, costi di archiviazione, e il costo per ottenere quando necessario.
  • "Ordinamento cache" è un nome adatto.
  • Non lo so.

Inoltre, quando si memorizzare nella cache le cose, li memorizzare nella cache al livello più generica possibile. Alcune cose potrebbe essere specifico utente (ad esempio i risultati di una query di ricerca), dove altri potrebbero essere facile da agnostico, come ad esempio la navigazione di un catalogo. Entrambi possono beneficiare di caching. La query catalogo potrebbe essere frequente e risparmiare un po 'di volta in volta, e la query di ricerca può essere costoso e risparmiare un sacco un paio di volte.

Risposto il 09/02/2011 a 08:00
fonte dall'utente

voti
0

Ci sono tanti modi per fare questo, come per la pelle del gatto proverbiale. Quindi, ecco un paio di mine.

Se la query originale è costoso, che si dice che è, quindi creare un'altra tabella, eventualmente, una tabella di memoria popolato con i risultati della vostra costosi e raramente eseguire query principale.

Questa seconda tabella potrebbe quindi essere interrogato in ogni visualizzazione e la selezione è semplice come impostare l'ordinamento appropriato.

Come richiesto ripopolare seconda tabella con i risultati della prima tabella, mantenendo così i dati aggiornati, ma minimizzando l'uso di query costoso.

In alternativa, se si vuole evitare anche la connessione al db allora si potrebbe memorizzare tutti i dati in un array PHP e memorizzarlo con memcached. questo sarebbe molto veloce e ha fornito le vostre liste non erano troppo grande sarebbe stato efficiente delle risorse. e può essere facilmente ordinati.

DC

Risposto il 11/02/2011 a 05:19
fonte dall'utente

voti
0

Assunzioni base:

  • Speciali sono settimanali
  • Possiamo aspettarci che il sito di cambiare frequentemente ... probabilmente tutti i giorni?
  • Siamo in grado di controllare gli aggiornamenti al database con etere un API o di rispondere tramite trigger

Se il sito cambia su una base quotidiana, suggerisco che tutte le pagine sono generate in modo statico durante la notte. Una query per ogni itera sorta di ordine attraverso e fa tutte le pagine relative. Anche se ci sono elementi dinamici, le probabilità sono che si possono affrontare includendo gli elementi della pagina statica. Ciò fornirebbe un servizio pagina ottimale e senza carico del database. In realtà, si potrebbe generare pagine separate e PREV / NEXT elementi che sono inclusi nelle pagine. Questo può essere più pazzo con 200 modi per ordinare, ma con 3 io sono un grande fan di esso.

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

Se per qualche motivo questo non è possibile, mi piacerebbe ricorrere a memorizzazione. Memcache è popolare per questo genere di cose (gioco di parole!). Quando qualcosa è spinto al database, è possibile emettere un trigger per aggiornare la cache con i valori corretti. Fate questo nello stesso modo in cui si se, come se il vostro articolo aggiornato esistesse in 3 liste collegate - ricollegare a seconda dei casi (this.next.prev = this.prev, ecc). Da che, fino a quando la cache non riempire eccessivamente, sarete tirando valori semplici dalla memoria in un modo chiave primaria.

Questo metodo ci vorrà un po 'di codice in più sui metodi di selezione e di aggiornamento / inserimento, ma dovrebbe essere piuttosto limitata. Alla fine, sarete alla ricerca up [id of tomatoes class 1].price.next. Se la chiave è nella cache, d'oro. In caso contrario, inserire nella cache e la visualizzazione.

  • Pensi che questa è una buona pratica per scoprire i record vicini per diversi ordini di query? Sì. E 'saggio per eseguire look-aheads sulle prossime richieste attesi.
  • Sai le pratiche migliori in termini di prestazioni e semplicità? Sai una cosa che rende questo del tutto obsoleto? Speriamo che quanto sopra
  • In teoria la programmazione, c'è un nome per questo problema? Ottimizzazione?
  • È il nome di "cache Ordinamento" è appropriato e comprensibile per questa tecnica? Non sono sicuro di uno specifico nome appropriato. Si tratta di caching, è una cache di sorta, ma non sono sicuro che mi dice di avere un "cache di smistamento" sarebbe trasmettere la comprensione immediata.
  • Ci sono riconosciuti, modelli comuni per risolvere questo problema? Come si chiamano? Caching?

Siamo spiacenti mie risposte tailing sono un po 'inutile, ma credo che le mie soluzioni narrative dovrebbe essere abbastanza utile.

Risposto il 11/02/2011 a 18:13
fonte dall'utente

voti
0

Si potrebbe salvare i numeri di riga delle liste ordinate in vista , e si potrebbe raggiungere le voci precedenti e successivi nella lista sotto (current_rownum-1) e numeri di riga (current_rownum + 1).

Risposto il 12/02/2011 a 14:01
fonte dall'utente

voti
0

Il problema / datastructur è chiamato grafico bidirezionale o si potrebbe dire che hai più liste collegate.

Se si pensa di esso come una lista collegata, si può solo aggiungere i campi alla tabella degli elementi per ogni ordinamento e prev / next chiave. Ma la persona DB ti ucciderà per questo, è come GOTO.

Se si pensa di esso come (bi) grafico direzionale, si va con la risposta di Jessica. Il problema principale è che gli aggiornamenti ordine sono operazioni costose.

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

Se si modifica la posizione di uno voci al nuovo ordine A, C, B, D, si dovrà aggiornare 4 righe.

Risposto il 13/02/2011 a 02:20
fonte dall'utente

voti
4

Ho un'idea in qualche modo simile a Jessica di. Tuttavia, invece di memorizzare i collegamenti alle voci di ordinamento precedente e successiva, si memorizza il tipo di ordinamento per ogni tipo di ordinamento. Per trovare il record precedente o successivo, basta avere la riga con SortX = currentSort ++ o SortX = currentSort--.

Esempio:

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

Questa soluzione produrrebbe tempi di query molto brevi, e si occupano meno spazio su disco rispetto all'idea di Jessica. Tuttavia, come sono sicuro che ti rendi conto, il costo di aggiornamento di una riga di dati è notevolmente superiore, poiché è necessario ricalcolare e memorizzare tutti i tipi di ordinamento. Ma ancora, a seconda della situazione, se gli aggiornamenti dei dati sono rari e soprattutto se capita sempre alla rinfusa, allora questa soluzione potrebbe essere il migliore.

vale a dire

once_per_day
  add/delete/update all records
  recalculate sort orders

Spero che questo è utile.

Risposto il 13/02/2011 a 03:30
fonte dall'utente

voti
0

Mi scuso se ho capito male, ma penso che si desidera conservare l'elenco ordinato tra utente accede al server. Se è così, la risposta potrebbe trovarsi nella vostra strategia e delle tecnologie di caching piuttosto che in ottimizzazione delle query / schema del database.

Il mio approccio sarebbe serializzare () l'array una volta recuperata la prima, e poi cache che in un'area di memorizzazione separata; Se questo è memcached / APC / hard-drive / MongoDB / ecc e mantenere i suoi dettagli della posizione della cache per ogni utente individualmente attraverso i loro dati di sessione. Il backend di memorizzazione effettiva sarebbe naturalmente dipende dalla dimensione della matrice, che non si va troppo nei dettagli circa, ma le scale memcached grande su più server e mongo ancora di più ad un costo leggermente maggiore latenza.

Inoltre non indicare il numero di permutazioni sorta ci sono nel mondo reale; ad esempio, si deve fornire in cache liste separate per utente, o si può globalmente di cache per ogni tipo di permutazione e poi filtrare ciò che non è necessario tramite PHP ?. Nell'esempio si dà, avrei semplicemente di cache sia permutazioni e negozio di quale dei due che avevo bisogno di unserialize () nei dati di sessione.

Quando l'utente ritorna al sito, controllare il tempo di vivere il valore dei dati memorizzati nella cache e ri-utilizzarlo se ancora valido. Mi piacerebbe anche avere un trigger in esecuzione su INSERT IGNORARE / UPDATE / DELETE per le offerte speciali che imposta semplicemente un campo timestamp in una tabella separata. Ciò indica immediatamente se la cache era viziata e la query doveva essere ri-run per un costo molto basso interrogazione. La cosa grande circa utilizzando solo il grilletto per impostare un singolo campo è che non c'è bisogno di preoccuparsi di potatura vecchi valori / ridondanti fuori da quel tavolo.

Se questo è adatto dipenderebbe dalle dimensioni dei dati che vengono restituiti, come spesso è stato modificato, e ciò che le tecnologie di caching sono disponibili sul server.

Risposto il 13/02/2011 a 15:47
fonte dall'utente

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