it-swarm.com.de

Streaming großer Ergebnismengen mit MySQL

Ich entwickle eine Frühlingsanwendung, die große MySQL-Tabellen verwendet. Beim Laden großer Tabellen erhalte ich eine OutOfMemoryException, da der Treiber versucht, die gesamte Tabelle in den Anwendungsspeicher zu laden.

Ich habe es versucht

statement.setFetchSize(Integer.MIN_VALUE);

aber dann hängt jedes ResultSet, das ich öffne, an close(); Beim Online-Suchen habe ich festgestellt, dass dies geschieht, weil versucht wird, alle ungelesenen Zeilen zu laden, bevor das ResultSet geschlossen wird.

ResultSet existingRecords = getTableData(tablename);
try {
    while (existingRecords.next()) {
        // ...
    }
} finally {
    existingRecords.close(); // this line is hanging, and there was no exception in the try clause
}

Das hängt auch für kleine Tabellen (3 Zeilen) und wenn ich das RecordSet nicht schließe (was in einer Methode passiert ist), dann hängt connection.close().


Stapelspur des Hanges:

SocketInputStream.socketRead0 (FileDescriptor, Byte [], Int, Int, Int) Zeile: nicht verfügbar [native Methode]
SocketInputStream.read (Byte [], Int, Int) Zeile: 129
ReadAheadInputStream.fill (int) -Zeile: 113
ReadAheadInputStream.readFromUnderlyingStreamIfNecessary (Byte [], int, int) Zeile: 160
ReadAheadInputStream.read (Byte [], Int, Int) Zeile: 188
MysqlIO.readFully (InputStream, Byte [], Int, Int) Zeile: 2428 MysqlIO.reuseAndReadPacket (Puffer, int) Zeile: 2882
MysqlIO.reuseAndReadPacket (Puffer) Zeile: 2871
MysqlIO.checkErrorPacket (int) Zeile: 3414
MysqlIO.checkErrorPacket () Zeile: 910
MysqlIO.nextRow (Feld [], int, boolean, int, boolean, boolean, boolean, Puffer) Zeile: 1405
RowDataDynamic.nextRecord () Zeile: 413
RowDataDynamic.next () Zeile: 392 Zeile RowDataDynamic.close (): 170
JDBC4ResultSet (ResultSetImpl) .realClose (boolean) Zeile: 7473 JDBC4ResultSet (ResultSetImpl) .close () Zeile: 881 Zeile "DelegatingResultSet.close ()": 152
Zeile "DelegatingResultSet.close ()": 152
DelegatingPreparedStatement (DelegatingStatement) .close () Zeile: 163
(Dies ist meine Klasse) Database.close () Zeile: 84 

41
configurator

Schließen Sie Ihre ResultSets nicht zweimal.

Offensichtlich versucht es beim Schließen einer Statement, die entsprechende ResultSet zu schließen, wie Sie in den beiden Zeilen des Stack-Trace sehen können:

Zeile "DelegatingResultSet.close ()": 152
DelegatingPreparedStatement (DelegatingStatement) .close () Zeile: 163

Ich hatte geglaubt, der Hang sei in ResultSet.close(), aber er war tatsächlich in Statement.close(), was ResultSet.close() aufruft. Da die ResultSet bereits geschlossen war, hing es einfach.

Wir haben alle ResultSet.close() durch results.getStatement().close() ersetzt und alle Statement.close()s entfernt. Das Problem ist nun behoben.

12
configurator

Nur das Einstellen der Abrufgröße ist nicht der richtige Ansatz. Der javadoc von Statement#setFetchSize() sagt bereits Folgendes aus:

Gibt dem JDBC-Treiber ein hint in Bezug auf die Anzahl der Zeilen, die aus der Datenbank abgerufen werden sollen.

Der Treiber kann den Hinweis tatsächlich anwenden oder ignorieren. Einige Treiber ignorieren es, einige Treiber wenden es direkt an, einige Treiber benötigen mehr Parameter. Der MySQL-JDBC-Treiber fällt in die letzte Kategorie. Wenn Sie die MySQL JDBC-Treiberdokumentation überprüfen, werden folgende Informationen angezeigt (scrollen Sie ungefähr 2/3 nach unten, bis der Header ResultSet erscheint:

Um diese Funktionalität zu aktivieren, müssen Sie eine Statement-Instanz auf folgende Weise erstellen:

stmt = conn.createStatement(Java.sql.ResultSet.TYPE_FORWARD_ONLY, Java.sql.ResultSet.CONCUR_READ_ONLY);
stmt.setFetchSize(Integer.MIN_VALUE);

Bitte lesen Sie den Abschnitt gesamt des Dokuments. Er beschreibt auch die Vorbehalte dieses Ansatzes. Hier ist ein relevanter Zitat:

Bei diesem Ansatz gibt es einige Einschränkungen. Sie müssen alle Zeilen in der Ergebnismenge lesen (oder sie schließen), bevor Sie andere Abfragen für die Verbindung ausgeben können. Andernfalls wird eine Ausnahme ausgelöst. 

(...)

Wenn sich die Anweisung im Gültigkeitsbereich einer Transaktion befindet, werden Sperren freigegeben, wenn die Transaktion abgeschlossen ist (was bedeutet, dass die Anweisung zuerst abgeschlossen werden muss). Wie bei den meisten anderen Datenbanken sind Anweisungen nicht vollständig, bis alle für die Anweisung ausstehenden Ergebnisse gelesen oder die aktive Ergebnismenge für die Anweisung geschlossen ist. 

Wenn die Variable OutOfMemoryError nicht behoben wird (nicht Exception), liegt das Problem wahrscheinlich darin, dass Sie alle Daten in Javas Speicher speichern, anstatt sie sofort zu verarbeiten, sobald die Daten eingehen. Dies würde mehr erfordern Änderungen in Ihrem Code, möglicherweise eine vollständige Umschreibung. Ich habe ähnliche Frage schon vorher hier beantwortet.

55
BalusC

Falls jemand dasselbe Problem hat, habe ich es mit der LIMIT-Klausel in meiner Abfrage gelöst. 

Dieses Problem wurde MySql als Fehler gemeldet (finden Sie hier http://bugs.mysql.com/bug.php?id=42929 ), der jetzt den Status "Kein Fehler" hat. Der relevanteste Teil ist:

Es gibt derzeit keine Möglichkeit, eine Ergebnismenge "midstream" zu schließen.

Da Sie ALLE Zeilen lesen müssen, müssen Sie Ihre Abfrageergebnisse mit einer Klausel wie WHERE oder LIMIT einschränken. Alternativ können Sie Folgendes versuchen:

ResultSet rs = ...
while(rs.next()) {
   ...
   if(bailOut == true) { break; }
}

while(rs.next()); // This will deplete the remaining rows on the stream

rs.close();

Es ist vielleicht nicht ideal, aber es bringt Sie zumindest dazu, nahe dran zu sein.

4
Rooney

Wenn Sie spring jdbc verwenden, müssen Sie in Verbindung mit SimpleJdbcTemplate einen vorbereiteten Anweisungsersteller verwenden, um fetchSize auf Integer.MIN_VALUE festzulegen. Seine hier beschriebenen http://neopatel.blogspot.com/2012/02/mysql-jdbc-driver-and-streaming-large.html

1
kalpesh

Das scrollbare Resultset ignoriert fetchSize und ruft alle Zeilen auf einmal ab, was zu einem Fehler führt.

Bei mir funktionierte es einwandfrei, wenn useCursors = true gesetzt wurde. Andernfalls ignoriert The Scrollable Resultset alle Implementierungen der Abrufgröße. In meinem Fall waren es 5000, aber Scrollable Resultset hat Millionen von Datensätzen auf einmal abgerufen, was eine übermäßige Speicherauslastung verursacht. zugrunde liegende DB ist MSSQLServer.

jdbc: jtds: sqlserver: // localhost: 1433/ACS; TDS = 8.0; useCursors = true

0
manu

Es hängt davon ab, dass die Anforderung auch dann aufhört, wenn Sie aufhören, zu hören .. Um das ResultSet und die Anweisung in der richtigen Reihenfolge zu schließen, rufen Sie zuerst statement.cancel () auf:

public void close() {
    try {
        statement.cancel();
        if (resultSet != null)
            resultSet.close();
    } catch (SQLException e) {
        // ignore errors on closing
    } finally {
        try {
            statement.close();
        } catch (SQLException e) {
            // ignore errors on closing
        } finally {
            resultSet = null;
            statement = null;
        }
    }
}
0
loic.jaouen