2015-12-13 9 views
6

Im using mysql con JDBC.Ottimizzazione della query MySQL su una tabella di grandi dimensioni

Ho una tabella di esempio di grandi dimensioni che contiene 6,3 milioni di righe su cui sto cercando di eseguire query di selezione efficienti. Vedere di seguito: enter image description here

ho creato tre indici aggiuntivi sul tavolo, vedere sotto: enter image description here

Esecuzione di una query SELECT come questo SELECT latitude, longitude FROM 3dag WHERE timestamp BETWEEN "+startTime+" AND "+endTime+" AND HourOfDay=4 AND DayOfWeek=3" ha un tempo di esecuzione che è estremamente elevata a 256356 ms, o un po ' più di quattro minuti. Il mio contare sulla stessa query mi dà questo: enter image description here

Il mio codice per il recupero dei dati è di seguito:

Connection con = null; 
    PreparedStatement pst = null; 
    Statement stmt = null; 
    ResultSet rs = null; 

    String url = "jdbc:mysql://xxx.xxx.xxx.xx:3306/testdb"; 
    String user = "bigd"; 
    String password = "XXXXX"; 

    try { 
     Class.forName("com.mysql.jdbc.Driver"); 
     con = DriverManager.getConnection(url, user, password); 
     String query = "SELECT latitude, longitude FROM 3dag WHERE timestamp BETWEEN "+startTime+" AND "+endTime+" AND HourOfDay=4 AND DayOfWeek=3"; 
     stmt = con.prepareStatement("SELECT latitude, longitude FROM 3dag WHERE timestamp>=" + startTime + " AND timestamp<=" + endTime); 
     stmt = con.createStatement(java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY); 
     stmt.setFetchSize(Integer.MIN_VALUE); 
     rs = stmt.executeQuery(query); 

     System.out.println("Start"); 
     while (rs.next()) { 

      int tempLong = (int) ((Double.parseDouble(rs.getString(2))) * 100000); 
      int x = (int) (maxLong * 100000) - tempLong; 
      int tempLat = (int) ((Double.parseDouble(rs.getString(1))) * 100000); 
      int y = (int) (maxLat * 100000) - tempLat; 

      if (!(y > matrix.length) || !(y < 0) || !(x > matrix[0].length) || !(x < 0)) { 
       matrix[y][x] += 1; 
      } 
     } 
     System.out.println("End"); 
     JSONObject obj = convertToCRS(matrix); 
     return obj; 

    }catch (ClassNotFoundException ex){ 
     Logger lgr = Logger.getLogger(Database.class.getName()); 
     lgr.log(Level.SEVERE, ex.getMessage(), ex); 
     return null; 
    } 
    catch (SQLException ex) { 
     Logger lgr = Logger.getLogger(Database.class.getName()); 
     lgr.log(Level.SEVERE, ex.getMessage(), ex); 
     return null; 
    } finally { 
     try { 
      if (rs != null) { 
       rs.close(); 
      } 
      if (pst != null) { 
       pst.close(); 
      } 
      if (con != null) { 
       con.close(); 
      } 
     } catch (SQLException ex) { 
      Logger lgr = Logger.getLogger(Database.class.getName()); 
      lgr.log(Level.WARNING, ex.getMessage(), ex); 
      return null; 
     } 
    } 

Rimozione ogni linea nel ciclo while(rs.next()) mi dà la stessa orribile run-time.

La mia domanda è cosa posso fare per ottimizzare questo tipo di query? Sono curioso di sapere lo .setFetchSize() e quale dovrebbe essere il valore ottimale qui. La documentazione mostra che INTEGER.MIN_VALUE risulta nel recupero riga per riga, è corretto?

Qualsiasi aiuto è apprezzato.

EDIT Dopo la creazione di un nuovo indice sul timestamp, DayOfWeek e HourOfDay mia domanda corre 1 minuto più veloce e spiegare mi dà questo:

enter image description here

+1

Si prega di evitare schermate e mostrare le informazioni in formato testo. È molto più facile leggere e lavorare con. –

+0

Oh, pensavo che sarebbe stato il contrario. Si tratta di una linea guida SO, o semplicemente delle tue preferenze personali? – kongshem

+3

Un motivo è che non è possibile copiare e incollare da un'immagine :-) –

risposta

1

Alcune idee su fronte:

  • Hai effettivamente controllato il tempo di esecuzione SQL (da .executeQuery() fino alla prima riga?) O è l'esecuzione + iterazione su 6,3 milioni di righe?
  • Preparate un PreparedStatement ma non usarlo ?!
  • Usa PreparedStatement, passano tiemstamp, dayOfWeek, hourOfDay come parametri
  • Creare uno indice che può soddisfare la vostra condizione in cui. Ordina le chiavi in ​​modo da poter eliminare la maggior parte degli articoli con il campo più alto.

del Idex potrebbe essere simile:

CREATE INDEX stackoverflow on 3dag(hourOfDay, dayOfWeek, Timestamp); 

Eseguire SQL all'interno MySQL - che ora ci si arriva?

  • Provare senza stmt.setFetchSize(Integer.MIN_VALUE); questo potrebbe creare molti roundtrips rete non necessari.
+0

L'esecuzione della query nella riga di comando mi dà lo stesso tempo di esecuzione. La creazione di un indice come suggerito ora, riporterà i risultati. Per quanto riguarda PreparedStatement, Im non è in grado di impostare 'java.sql.ResultSet.TYPE_FORWARD_ONLY, java.sql.ResultSet.CONCUR_READ_ONLY', ecco perché ho usato Statement. – kongshem

+0

Bene, quale numero di linee si prevede di restituire? Se non riempie la tua memoria, potresti comunque risparmiare con le opzioni predefinite. – Jan

+0

Un altro pensiero: forzare MySQL allo streaming potrebbe essere più lento del semplice pompaggio dei risultati in memoria. – Jan

1

Secondo la vostra domanda, la cardinalità (cioè il numero di valori distinti nella colonna) Timestamp è di circa 1/30 della cardinalità della colonna Uid. Cioè, hai un sacco di timestamp identici. Ciò non promette nulla di buono per l'efficienza della tua query.

Detto questo, si potrebbe tentare di utilizzare il seguente compound covering index per accelerare le cose.

CREATE INDEX 3dag_q ON ('Timestamp' HourOfDay, DayOfWeek, Latitude, Longitude) 

Perché questo aiuto? Perché l'intera query può essere soddisfatta dall'indice con una cosiddetta scansione dell'indice stretto. Il motore di query MySQL accederà in modo casuale all'indice alla voce con il valore di Timestamp più piccolo corrispondente alla query. Quindi leggerà l'indice in ordine e tirerà fuori la latitudine e la longitudine dalle righe che corrispondono.

Si potrebbe provare a fare alcuni dei riassuntivo sul server MySQL.

SELECT COUNT(*) number_of_duplicates, 
     ROUND(Latitude,4) Latitude, ROUND(Longitude,4) Longitude 
    FROM 3dag 
WHERE timestamp BETWEEN "+startTime+" 
        AND "+endTime+" 
    AND HourOfDay=4 
    AND DayOfWeek=3 
GROUP BY ROUND(Latitude,4), ROUND(Longitude,4) 

Questo potrebbe restituire un set di risultati inferiore. Modifica Questo quantizza (arrotonda) i valori lat/long e quindi conta il numero di elementi duplicati arrotondandoli. Più grossolanemente le arrotondate (vale a dire, più piccolo è il secondo numero nelle chiamate alla funzione ROUND(val,N)) più valori duplicati che incontrerete e meno righe distinte verranno generate dalla vostra query. Meno righe risparmiano tempo.

Infine, se questi valori di lat/long sono derivati ​​GPS e registrate in gradi, non ha senso per cercare di affrontare con più di circa quattro o cinque cifre decimali. La precisione del GPS commerciale è limitata a questo.

Altre proposte

Fai la tua latitudine e longitudine colonne in FLOAT valori nella tabella se hanno la precisione del GPS. Se hanno maggiore precisione del GPS, usa DOUBLE. Memorizzare e trasferire numeri nelle colonne varchar(30) è abbastanza inefficiente.

Analogamente, creare le colonne HourOfDay e DayOfWeek in SMALLINT o anche i tipi di dati TINYINT nella tabella. Gli interi a 64 bit per i valori compresi tra 0 e 31 sono inutili. Con centinaia di file, non importa. Con milioni lo fa.

Infine, se le vostre domande sembrano sempre così

SELECT Latitude, Longitude 
    FROM 3dag 
    WHERE timestamp BETWEEN SOME_VALUE 
         AND ANOTHER_VALUE 
    AND HourOfDay = SOME_CONSTANT_DAY 
    AND DayOfWeek = SOME_CONSTANT_HOUR 

questo composto che copre indice dovrebbe essere ideale per accelerare la query.

CREATE INDEX 3dag_hdtll ON (HourOfDay, DayofWeek, `timestamp`, Latitude, Longitude) 
+0

Penso che tu abbia letto la mia matrice errata, la dimensione è 1371 x 838 :) Sto usando 5 decimali per ogni valore lat long. Grazie comunque. Guarderò nel suggerimento riassuntivo. – kongshem

+0

Prova l'indice di copertura –

+0

Ok, quindi il risultato della query che hai proposto mi ha dato un tempo di esecuzione di 2 minuti e 11 secondi. Che cosa ha fatto esattamente questa query?Ha contato i valori di latitudine e longitudine risultanti con quattro decimali? Una rapida analisi dell'output mostra che molti dei valori lat long hanno avuto 1 occorrenza, la maggior parte di essi ha avuto tra 10 e 50 occorrenze e il più grande era circa 400. – kongshem

0

Sto estrapolando dalla mia app di tracciamento. Questo è quello che faccio per l'efficienza:

In primo luogo, una possibile soluzione dipende dal fatto che o non si può prevedere/controllare gli intervalli di tempo. Archiviare istantanee ogni X minuti o una volta al giorno, ad esempio. Supponiamo che tu voglia visualizzare tutti gli eventi YESTERDAY. È possibile salvare un'istantanea che ha già filtrato il file. Ciò accelera enormemente le cose, ma non è una soluzione praticabile per intervalli di tempo personalizzati e copertura reale dal vivo.

La mia domanda è vivere, ma di solito funziona abbastanza bene in T + 5 minuti (5 minuti al massimo lag/delay). Solo quando l'utente sceglie effettivamente la visualizzazione della posizione live l'applicazione aprirà una query completa sul db live. Quindi, dipende da come funziona la tua app.

secondo fattore: come si intende usare il timestamp è molto importante. Evitare ad esempio VARCHAR. Se stai convertendo UNIXTIME anche questo ti darà un lagtime non necessario.Dal momento che stai sviluppando quella che sembra essere un'applicazione di geotracking, il tuo timestamp sarà in un unico momento - un numero intero. alcuni dispositivi funzionano con millisecondi, mi raccomando di non usarli. 1449878400 invece di 1449878400000 (2015/12/12 0 GMT)

risparmio tutti i miei datetimes GEOPOINT in pochi secondi unixtime e usare MySQL timestamp solo per timestamping il momento il punto è stato ricevuto dal server (che è irrilevante per questa query proponete).

È possibile ridurre il tempo di accesso a una vista indicizzata anziché eseguire una query completa. Se quel tempo è significativo in una query di grandi dimensioni è soggetto a test.

Infine, si potrebbe radere un bitsy itsy più non usando BETWEEN e l'utilizzo di qualcosa di simile a ciò che sarà traducono in (pseudo sotto)

WHERE (timecode > start_Time AND timecode < end_time) 

vedere che ho cambiare >= e <=-> e < perché è probabile che il tuo timestamp non sarà quasi mai il secondo preciso e, anche se lo fosse, raramente ti affliggeresti se 1 evento geopoint/time è o non è visualizzato.

+1

Attualmente sto inserendo i dati in una nuova tabella con i miglioramenti proposti dei tipi di dati. L'ho fatto su un set leggermente più grande di dati con 32 milioni di righe, quindi l'inserimento richiede un po 'di tempo. Tornerò da te, grazie! – kongshem

+1

Oh, e il timestamp nella mia tabella è unix timestamp :) – kongshem

+0

sì, l'ho visto. colpa mia. –

Problemi correlati