Datenbanksortierung vs. programmatische Java-Sortierung

Lesezeit: 3 Minuten

Benutzer-Avatar
Moro

Ich möchte Daten aus der Datenbank (MySQL) per JPA abrufen, ich möchte, dass sie nach einem Spaltenwert sortiert werden.

Also, was ist die beste Vorgehensweise, um:

  • Rufen Sie die Daten aus der Datenbank als Liste von Objekten (JPA) ab und sortieren Sie sie dann programmgesteuert mithilfe einiger Java-APIs.

ODER

  • Lassen Sie es von der Datenbank sortieren, indem Sie eine sortierende Auswahlabfrage verwenden.

Danke im Voraus

  • Manchmal ist es keine einfache Technologiewahl, in meinem Fall ist die Datenbank sehr beschäftigt und wird höchstwahrscheinlich zum Engpass, also muss ich den Speicher sortieren.

    – Auf Wiedersehen

    19. August 2015 um 6:35 Uhr

Benutzer-Avatar
Gennadiy

Wenn Sie eine Teilmenge aller Datenbankdaten abrufen, z. B. 20 von 1000 Zeilen auf dem Bildschirm anzeigen, ist es besser, in der Datenbank zu sortieren. Dies ist schneller und einfacher und ermöglicht es Ihnen, eine Seite mit Zeilen (20, 50, 100) auf einmal statt alle abzurufen.

Wenn Ihr Datensatz relativ klein ist, kann das Sortieren in Ihrem Code bequemer sein, wenn Sie eine komplexe Sortierung implementieren möchten. Normalerweise kann diese komplexe Sortierung durchgeführt werden SQL aber nicht so einfach wie im Code.

Kurz gesagt, die Faustregel lautet Sortieren über SQLmit einigen Grenzfällen zur Regel.

  • Ich muss der “Faustregel” widersprechen. Das Sortieren auf der Datenebene ist teuer, da der typische Anwendungsfall darin besteht, dass möglicherweise mehrere Sortierreihenfolgen derselben Ergebnismenge erforderlich sind. Das Sortieren auf der Anwendungsebene, dh zum Ändern der Anzeigereihenfolge der Daten in der Präsentationsebene, ist viel sinnvoller aus Sicht der Bequemlichkeit und Skalierbarkeit.

    – Orlando Colamatteo

    15. März 2013 um 19:37 Uhr

  • Was ist, wenn ich gegen API/Dienste vorgehe, um meine Daten zu erhalten? und insbesondere beinhaltet es mehr als einen API-Aufruf.

    – Matt

    21. Mai 2016 um 0:03 Uhr


  • Wenn Sie eine Datenbanksortierung durchführen und 20, 50 oder 100 gleichzeitig zurückgeben, wird nur diese kleine Teilmenge sortiert oder erfolgt die Sortierung vorher für alle Datensätze in der Datenbank?

    – hipokito

    25. Februar 2019 um 19:00 Uhr

Im Allgemeinen ist es besser, wenn Sie verwenden ORDER BY in Ihrer SQL-Abfrage — auf diese Weise erhalten Sie, wenn es einen anwendbaren Index gibt, Ihre Sortierung möglicherweise “kostenlos” (im schlimmsten Fall ist dies der gleiche Arbeitsaufwand wie in Ihrem Code, aber häufig ist dies der Fall weniger Arbeit als das!).

  • Der typische Anwendungsfall besteht darin, dass eine einzelne Ergebnismenge mehrere Anforderungen an die Sortierreihenfolge hat, sodass der Nutzen eines Index begrenzt ist, es sei denn, Sie sind bereit, jede Sortieranforderung jeder Abfrage mit einem Index abzudecken … was für die meisten Systeme nicht realistisch ist. Das Sortieren in der Anwendungsebene, dh die Möglichkeit, die Anzeigereihenfolge einer bestimmten Ergebnismenge beliebig zu ändern, wie der Benutzer entscheidet, dass sie sortiert werden soll, ohne eine weitere Anfrage an die Datenebene zu stellen, ist aus Sicht der Benutzerfreundlichkeit und Skalierbarkeit viel sinnvoller.

    – Orlando Colamatteo

    23. April 2013 um 16:53 Uhr

  • @opc.three Erstens stimme ich nicht zu, dass es sich um einen typischen Anwendungsfall handelt, es hängt von Ihrer App ab. Zweitens, was ist, wenn Sie eine Teilmenge von Big Data abrufen? Würden Sie all diese Millionen von Datensätzen aus einer Datenquelle abrufen und mit ihnen im Gedächtnis umgehen? Und zuletzt hätte ich keine Angst davor, Anfragen an eine Datenschicht zu senden, da Sie dies sowieso oft tun müssen (z. B. wenn ein Benutzer andere Suchkriterien eingibt) und weil Sie immer Caching oder spezielle Systeme (z. B. solr) verwenden können. wenn Sie die Suche/den Abruf beschleunigen möchten und db allein nicht ausreicht.

    – Lukasz Dumiszewski

    3. Dezember 2014 um 7:06 Uhr


  • >> Zweitens, was ist, wenn Sie eine Teilmenge von Big Data abrufen? Würden Sie all diese Millionen von Datensätzen aus einer Datenquelle abrufen und mit ihnen im Gedächtnis umgehen? << Das ist nicht der fragliche Anwendungsfall. In Ihrem Beispiel benötigen Sie ORDER BY, um das richtige Ergebnis zu erhalten. Im Beispiel im Originalkommentar dient ORDER BY einfach der Präsentationsebene ... Äpfel und Orangen. Sie können die Annahme verweigern, aber es ist ein typischer Anwendungsfall. Beim Zwischenspeichern und Sortieren in einer Anwendungsebene lassen Sie sich beim Hochskalieren mehr Optionen.

    – Orlando Colamatteo

    3. Dezember 2014 um 15:58 Uhr


Ich stieß auf dieselbe Frage und entschied, dass ich einen kleinen Benchmark durchführen sollte, um die Geschwindigkeitsunterschiede zu quantifizieren. Die Ergebnisse haben mich überrascht. Ich möchte meine Erfahrungen mit dieser Art von Frage posten.

Wie bei einigen anderen Postern hier dachte ich, dass die Datenbankschicht die Sortierung schneller erledigen würde, weil sie angeblich auf solche Dinge eingestellt ist. @Alex hat darauf hingewiesen, dass es schneller sein wird, wenn die Datenbank bereits einen Index für die Sortierung hat. Ich wollte die Frage beantworten, welche Rohsortierung bei nicht indizierten Sortierungen schneller ist. Beachten Sie, sagte ich schneller, nicht einfacher. Ich denke, in vielen Fällen ist es einfacher und weniger fehleranfällig, die Datenbank die Arbeit erledigen zu lassen.

Meine Hauptannahme war, dass die Art in den Hauptspeicher passen würde. Nicht alle Probleme passen hierher, aber eine gute Anzahl tut es. Bei Speichermangel kann es durchaus sein, dass hier Datenbanken glänzen, obwohl ich das nicht getestet habe. Im Fall von In-Memory-Sortierungen hat Java/C/C++ in meinem informellen Benchmark, wenn man es so nennen kann, MySQL übertroffen.

Ich wünschte, ich hätte mehr Zeit gehabt, um die Datenbankschicht und die Anwendungsschicht gründlicher zu vergleichen, aber leider riefen andere Aufgaben an. Trotzdem konnte ich nicht anders, als diese Notiz für andere aufzuzeichnen, die diesen Weg entlang reisen.

Als ich diesen Weg einschlug, begann ich, mehr Hürden zu sehen. Soll ich die Datenübertragung vergleichen? Wie? Kann ich die Zeit zum Lesen von DB mit der Zeit zum Lesen einer flachen Datei in Java vergleichen? Wie kann man die Sortierzeit gegenüber der Datenübertragungszeit gegenüber der Zeit zum Lesen der Datensätze isolieren? Mit diesen Fragen hier war die Methodik und die zeitlichen Zahlen, die ich mir ausgedacht habe.

Alle Zeiten in Millisekunden, sofern nicht anders angegeben

Alle Sortierroutinen waren die von der Sprache bereitgestellten Standardwerte (diese sind gut genug für zufällig sortierte Daten).

Die gesamte Zusammenstellung erfolgte mit einem typischen „Release-Profil“, das über Netbeans ausgewählt wurde, ohne Anpassung, sofern nicht anders angegeben

Alle Tests für mysql verwendeten das folgende Schema

 mysql> CREATE TABLE test_1000000
 (
 pk bigint(11) NOT NULL,
 float_value DOUBLE NULL,
 bigint_value     bigint(11)  NULL,
 PRIMARY KEY (pk )
 ) Engine MyISAM;

mysql> describe test_1000000;
+--------------+------------+------+-----+---------+-------+
| Field        | Type       | Null | Key | Default | Extra |
+--------------+------------+------+-----+---------+-------+
| pk           | bigint(11) | NO   | PRI | NULL    |       |
| float_value  | double     | YES  |     | NULL    |       |
| bigint_value | bigint(11) | YES  |     | NULL    |       |
+--------------+------------+------+-----+---------+-------+

Zuerst ist hier ein kleiner Ausschnitt, um die DB zu füllen. Es mag einfachere Wege geben, aber ich habe Folgendes getan:

public static void BuildTable(Connection conn, String tableName, long iterations) {
    Random ran = new Random();
    Math.random();
    try {


        long epoch = System.currentTimeMillis();
        for (long i = 0; i < iterations; i++) {
            if (i % 100000 == 0) {
                System.out.println(i + " next 100k");
            }
            PerformQuery(conn, tableName, i, ran.nextDouble(), ran.nextLong());
        }

    } catch (Exception e) {
        logger.error("Caught General Exception Error from main " + e);

    }
}

MYSQL Direct CLI-Ergebnisse:

select * from test_10000000 order by bigint_value limit 10;
10 rows in set (2.32 sec)

Diese Zeiten waren etwas schwierig, da die einzige Information, die ich hatte, die Zeit war, die nach der Ausführung des Befehls gemeldet wurde.

Von der MySQL-Eingabeaufforderung für 10000000 Elemente sind es ungefähr 2,1 bis 2,4, entweder zum Sortieren von bigint_value oder float_value

Java JDBC mysql-Aufruf (ähnliche Leistung wie beim Sortieren von mysql cli)

public static void SortDatabaseViaMysql(Connection conn, String tableName) {

    try {
        Statement stmt = conn.createStatement();
        String cmd = "SELECT * FROM " + tableName + " order by float_value limit 100";


        ResultSet rs = stmt.executeQuery(cmd);
    } catch (Exception e) {

    }

}

Fünf Läufe:

da=2379 ms
da=2361 ms
da=2443 ms
da=2453 ms
da=2362 ms

Java Sort On-Fly-Erzeugung von Zufallszahlen (war eigentlich langsamer als Disk-IO-Lesen). Die Zuweisungszeit ist die Zeit, um Zufallszahlen zu generieren und das Array zu füllen

Anrufen wie

JavaSort(10,10000000);

Timing-Ergebnisse:

assignment time 331  sort time 1139
assignment time 324  sort time 1037
assignment time 317  sort time 1028
assignment time 319  sort time 1026
assignment time 317  sort time 1018
assignment time 325  sort time 1025
assignment time 317  sort time 1024
assignment time 318  sort time 1054
assignment time 317  sort time 1024
assignment time 317  sort time 1017

Diese Ergebnisse galten für das Lesen einer Datei mit Doubles im Binärmodus

assignment time 4661  sort time 1056
assignment time 4631  sort time 1024
assignment time 4733  sort time 1004
assignment time 4725  sort time 980
assignment time 4635  sort time 980
assignment time 4725  sort time 980
assignment time 4667  sort time 978
assignment time 4668  sort time 980
assignment time 4757  sort time 982
assignment time 4765  sort time 987

Eine Pufferübertragung führt zu viel schnelleren Laufzeiten

assignment time 77  sort time 1192
assignment time 59  sort time 1125
assignment time 55  sort time 999
assignment time 55  sort time 1000
assignment time 56  sort time 999
assignment time 54  sort time 1010
assignment time 55  sort time 999
assignment time 56  sort time 1000
assignment time 55  sort time 1002
assignment time 56  sort time 1002

C- und C++-Timing-Ergebnisse (Quelle siehe unten)

Debug-Profil mit qsort

assignment 0 seconds 110 milliseconds   Time taken 2 seconds 340 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 2 seconds 340 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 2 seconds 330 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 2 seconds 340 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 2 seconds 330 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 2 seconds 340 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 2 seconds 340 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 2 seconds 330 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 2 seconds 340 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 2 seconds 330 milliseconds

Freigabeprofil mit qsort

assignment 0 seconds 100 milliseconds   Time taken 1 seconds 600 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 600 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 580 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 590 milliseconds
assignment 0 seconds 80 milliseconds    Time taken 1 seconds 590 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 590 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 600 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 590 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 600 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 1 seconds 580 milliseconds

Freigabeprofil Using std::sort( a, a + ARRAY_SIZE );

assignment 0 seconds 100 milliseconds   Time taken 0 seconds 880 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 0 seconds 870 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 0 seconds 890 milliseconds
assignment 0 seconds 120 milliseconds   Time taken 0 seconds 890 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 0 seconds 890 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 0 seconds 880 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 0 seconds 900 milliseconds
assignment 0 seconds 90 milliseconds    Time taken 0 seconds 890 milliseconds
assignment 0 seconds 100 milliseconds   Time taken 0 seconds 890 milliseconds
assignment 0 seconds 150 milliseconds   Time taken 0 seconds 870 milliseconds

Profil freigeben Zufallsdaten aus Datei lesen und std::sort( a, a + ARRAY_SIZE ) verwenden

assignment 0 seconds 50 milliseconds    Time taken 0 seconds 880 milliseconds
assignment 0 seconds 40 milliseconds    Time taken 0 seconds 880 milliseconds
assignment 0 seconds 50 milliseconds    Time taken 0 seconds 880 milliseconds
assignment 0 seconds 50 milliseconds    Time taken 0 seconds 880 milliseconds
assignment 0 seconds 40 milliseconds    Time taken 0 seconds 880 milliseconds

Unten ist der verwendete Quellcode. Hoffentlich minimale Bugs 🙂

Java-Quelle Beachten Sie, dass intern von JavaSort runCode und writeFlag angepasst werden müssen, je nachdem, was Sie zeitlich festlegen möchten. Beachten Sie auch, dass die Speicherzuweisung in der for-Schleife erfolgt (wodurch GC getestet wurde, aber ich habe keinen nennenswerten Unterschied beim Verschieben der Zuweisung außerhalb der Schleife festgestellt).

public static void JavaSort(int iterations, int numberElements) {

    Random ran = new Random();
    Math.random();
    int runCode = 2;
    boolean writeFlag = false;
    for (int j = 0; j < iterations; j++) {
        double[] a1 = new double[numberElements];
        long timea = System.currentTimeMillis();
        if (runCode == 0) {
            for (int i = 0; i < numberElements; i++) {
                a1[i] = ran.nextDouble();

            }
        }            
        else if (runCode == 1) {

            //do disk io!!
            try {
            DataInputStream in = new DataInputStream(new FileInputStream("MyBinaryFile.txt"));
            int i = 0;
            //while (in.available() > 0) {
            while (i < numberElements) { //this should be changed so that I always read in the size of array elements
                a1[i++] = in.readDouble();
            }
            }
            catch (Exception e) {

            }

        }
        else if (runCode == 2) {
            try  {
                FileInputStream stream = new FileInputStream("MyBinaryFile.txt");
                FileChannel inChannel = stream.getChannel();

                ByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, inChannel.size());
                //int[] result = new int[500000];

                buffer.order(ByteOrder.BIG_ENDIAN);
                DoubleBuffer doubleBuffer = buffer.asDoubleBuffer();
                doubleBuffer.get(a1);
            }
            catch (Exception e) {

            }
        }

        if (writeFlag) {
            try {
                DataOutputStream out = new DataOutputStream(new FileOutputStream("MyBinaryFile.txt"));
                for (int i = 0; i < numberElements; i++) {
                    out.writeDouble(a1[i]);
                }
            } catch (Exception e) {

            }
        }
        long timeb = System.currentTimeMillis();
        Arrays.sort(a1);

        long timec = System.currentTimeMillis();
        System.out.println("assignment time " + (timeb - timea) + " " + " sort time " + (timec - timeb));
        //delete a1;
    }
}

C/C++-Quelle

#include <iostream>
#include <vector>
#include <algorithm>
#include <fstream>

#include <cstdlib>
#include <ctime>
#include <cstdio>
#include <math.h>
#include <stdio.h>
#include <time.h>
#include <stdlib.h>

#define ARRAY_SIZE 10000000

using namespace std;

int compa(const void * elem1, const void * elem2) {
    double f = *((double*) elem1);
    double s = *((double*) elem2);
    if (f > s) return 1;
    if (f < s) return -1;
    return 0;
}

int compb (const void *a, const void *b) {
   if (*(double **)a < *(double **)b) return -1;
   if (*(double **)a > *(double **)b) return 1;
   return 0;
}

void timing_testa(int iterations) {

    clock_t start = clock(), diffa, diffb;

    int msec;
    bool writeFlag = false;
    int runCode = 1;

    for (int loopCounter = 0; loopCounter < iterations; loopCounter++) {
        double *a = (double *) malloc(sizeof (double)*ARRAY_SIZE);
        start = clock();
        size_t bytes = sizeof (double)*ARRAY_SIZE;
        if (runCode == 0) {
            for (int i = 0; i < ARRAY_SIZE; i++) {
                a[i] = rand() / (RAND_MAX + 1.0);
            }
        }
        else if (runCode == 1) {
            ifstream inlezen;

            inlezen.open("test", ios::in | ios::binary);


            inlezen.read(reinterpret_cast<char*> (&a[0]), bytes);

        }
        if (writeFlag) {
            ofstream outf;
            const char* pointer = reinterpret_cast<const char*>(&a[0]);
            outf.open("test", ios::out | ios::binary);
            outf.write(pointer, bytes);
            outf.close();

        }

        diffa = clock() - start;
        msec = diffa * 1000 / CLOCKS_PER_SEC;
        printf("assignment %d seconds %d milliseconds\t", msec / 1000, msec % 1000);
        start = clock();
        //qsort(a, ARRAY_SIZE, sizeof (double), compa);
        std::sort( a, a + ARRAY_SIZE );
        //printf("%f %f %f\n",a[0],a[1000],a[ARRAY_SIZE-1]);
        diffb = clock() - start;

        msec = diffb * 1000 / CLOCKS_PER_SEC;
        printf("Time taken %d seconds %d milliseconds\n", msec / 1000, msec % 1000);
        free(a);
    }



}

/*
 * 
 */
int main(int argc, char** argv) {

    printf("hello world\n");
    double *a = (double *) malloc(sizeof (double)*ARRAY_SIZE);


    //srand(1);//change seed to fix it
    srand(time(NULL));

    timing_testa(5);



    free(a);
    return 0;
}

  • Befindet sich die Datenbank auf derselben Box wie die getestete Anwendung? Wenn nicht, war die Hardware vergleichbar?

    – eaglei22

    9. November 2018 um 16:39 Uhr

  • Ja, die Datenbank befand sich auf derselben Box wie die Testanwendung.

    – Paulus

    9. November 2018 um 18:22 Uhr

Das ist nicht ganz richtig, aber ich habe kürzlich etwas gepostet, das sich auf die datenbank- vs. anwendungsseitige Sortierung bezieht. Der Artikel handelt von einer .net-Technik, daher wird das meiste davon wahrscheinlich nicht interessant für Sie sein, aber die Grundprinzipien bleiben bestehen:

Das Verschieben der Sortierung auf die Client-Seite (z. B. jQuery, Dataset/Dataview-Sortierung) mag verlockend erscheinen. Und es ist tatsächlich eine praktikable Option zum Paging, Sortieren und Filtern, wenn (und nur wenn):

1. der Datensatz ist klein, und

1. Es gibt wenig Bedenken hinsichtlich Leistung und Skalierbarkeit

Aus meiner Erfahrung gibt es nur wenige Systeme, die diese Art von Kriterien erfüllen. Beachten Sie, dass es nicht möglich ist, das Sortieren/Paging in der Anwendung/Datenbank zu mischen und abzugleichen. Wenn Sie die Datenbank nach 100 unsortierten Datenzeilen fragen und diese Zeilen dann auf der Anwendungsseite sortieren, werden Sie den Satz wahrscheinlich nicht erhalten von Daten, die Sie erwartet haben. Das mag offensichtlich erscheinen, aber ich habe den Fehler oft genug gesehen, dass ich ihn zumindest erwähnen wollte.

Es ist aus mehreren Gründen viel effizienter, in der Datenbank zu sortieren und zu filtern. Zum einen sind Datenbank-Engines hochoptimiert, um genau die Art von Arbeit zu erledigen, die das Sortieren und Filtern mit sich bringt; Dafür wurde ihr zugrunde liegender Code entwickelt. Aber selbst davon abgesehen – selbst wenn Sie Code schreiben könnten, der der Sortier-, Filter- und Paging-Leistung einer ausgereiften Datenbank-Engine entspricht – ist es immer noch vorzuziehen, diese Arbeit in der Datenbank zu erledigen, aus dem einfachen Grund, dass es effizienter ist, sie einzuschränken die Datenmenge, die von der Datenbank zum Anwendungsserver übertragen wird.

Wenn Sie beispielsweise vor dem Filtern 10.000 Zeilen haben und Ihre Abfrage diese Zahl auf 75 reduziert, führt das Filtern auf dem Client dazu, dass die Daten aus allen 10.000 Zeilen über die Leitung (und in den Speicher Ihres App-Servers) geleitet werden, wo die Filterung auf der Datenbankseite würde dazu führen, dass nur die gefilterten 75 Zeilen zwischen Datenbank und Anwendung verschoben werden. Dies kann einen großen Einfluss auf die Leistung und Skalierbarkeit haben.

Der vollständige Beitrag ist hier:
http://psandler.wordpress.com/2009/11/20/dynamic-search-objects-part-5sorting/

Ich bin mir fast sicher, dass es schneller geht, wenn die Datenbank sie sortieren kann. Es gibt Ingenieure, die viel Zeit damit verbringen, ihre Suchalgorithmen zu perfektionieren und zu optimieren, während Sie Ihren eigenen Sortieralgorithmus implementieren müssen, der möglicherweise einige weitere Berechnungen hinzufügt.

Benutzer-Avatar
Otávio Décio

Ich würde die Datenbank das Sortieren überlassen, darin sind sie im Allgemeinen sehr gut.

Benutzer-Avatar
Thorbjørn Ravn Andersen

Lassen Sie die Datenbank sortieren. Dann können Sie mit JPA problemlos paging haben, ohne die gesamte Ergebnismenge einzulesen.

1180480cookie-checkDatenbanksortierung vs. programmatische Java-Sortierung

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy