Mysql
 sql >> Datenbank >  >> RDS >> Mysql

Leistung von MySQL-Einfügungsanweisungen in Java:Vorbereitete Anweisungen im Stapelmodus vs. einzelne Einfügung mit mehreren Werten

JDBC ist einfach ein Java-SE-Standard für den Datenbankzugriff, der die Standardschnittstellen bietet, sodass Sie nicht wirklich an eine bestimmte JDBC-Implementierung gebunden sind. MySQL Java Connector (Connector/J) ist eine Implementierung der JDBC-Schnittstellen nur für MySQL-Datenbanken. Aus Erfahrung bin ich an einem Projekt beteiligt, das riesige Datenmengen mit MySQL verwendet, und wir bevorzugen MyISAM meistens für Daten, die generiert werden können:Es ermöglicht eine viel höhere Leistung bei Verlust von Transaktionen, aber im Allgemeinen ist MyISAM schneller, aber InnoDB ist zuverlässiger.

Ich habe mich vor etwa einem Jahr auch über die Leistung der INSERT-Anweisungen gewundert und den folgenden alten Testcode in meinem Code-Shelf gefunden (sorry, er ist etwas komplex und außerhalb Ihres Fragebereichs). Der folgende Code enthält Beispiele für 4 Möglichkeiten zum Einfügen der Testdaten:

  • einzeln INSERT s;
  • gestapelt INSERT s;
  • manueller Bulk INSERT (niemals verwenden - es ist gefährlich);
  • und schließlich vorbereitete Masse INSERT ).

Es verwendet TestNG als Runner und verwendet einige benutzerdefinierte Code-Legacy wie:

  • der runWithConnection() -Methode - stellt sicher, dass die Verbindung geschlossen oder in den Verbindungspool zurückgestellt wird, nachdem der Rückruf ausgeführt wurde (aber der folgende Code verwendet keine zuverlässige Strategie zum Schließen der Anweisung - auch ohne try). /finally um den Code zu reduzieren);
  • IUnsafeIn<T, E extends Throwable> - eine benutzerdefinierte Callback-Schnittstelle für die Methoden, die einen einzelnen Parameter akzeptieren, aber möglicherweise eine Ausnahme vom Typ E auslösen, wie:void handle(T argument) throws E; .
package test;

import test.IUnsafeIn;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import static java.lang.String.format;
import static java.lang.String.valueOf;
import static java.lang.System.currentTimeMillis;

import core.SqlBaseTest;
import org.testng.annotations.AfterSuite;
import org.testng.annotations.BeforeSuite;
import org.testng.annotations.BeforeTest;
import org.testng.annotations.Test;

public final class InsertVsBatchInsertTest extends SqlBaseTest {

    private static final int ITERATION_COUNT = 3000;

    private static final String CREATE_TABLE_QUERY = "CREATE TABLE IF NOT EXISTS ttt1 (c1 INTEGER, c2 FLOAT, c3 VARCHAR(5)) ENGINE = InnoDB";
    private static final String DROP_TABLE_QUERY = "DROP TABLE ttt1";
    private static final String CLEAR_TABLE_QUERY = "DELETE FROM ttt1";

    private static void withinTimer(String name, Runnable runnable) {
        final long start = currentTimeMillis();
        runnable.run();
        logStdOutF("%20s: %d ms", name, currentTimeMillis() - start);
    }

    @BeforeSuite
    public void createTable() {
        runWithConnection(new IUnsafeIn<Connection, SQLException>() {
            @Override
            public void handle(Connection connection) throws SQLException {
                final PreparedStatement statement = connection.prepareStatement(CREATE_TABLE_QUERY);
                statement.execute();
                statement.close();
            }
        });
    }

    @AfterSuite
    public void dropTable() {
        runWithConnection(new IUnsafeIn<Connection, SQLException>() {
            @Override
            public void handle(Connection connection) throws SQLException {
                final PreparedStatement statement = connection.prepareStatement(DROP_TABLE_QUERY);
                statement.execute();
                statement.close();
            }
        });
    }

    @BeforeTest
    public void clearTestTable() {
        runWithConnection(new IUnsafeIn<Connection, SQLException>() {
            @Override
            public void handle(Connection connection) throws SQLException {
                final PreparedStatement statement = connection.prepareStatement(CLEAR_TABLE_QUERY);
                statement.execute();
                statement.close();
            }
        });
    }

    @Test
    public void run1SingleInserts() {
        withinTimer("Single inserts", new Runnable() {
            @Override
            public void run() {
                runWithConnection(new IUnsafeIn<Connection, SQLException>() {
                    @Override
                    public void handle(Connection connection) throws SQLException {
                        for ( int i = 0; i < ITERATION_COUNT; i++ ) {
                            final PreparedStatement statement = connection.prepareStatement("INSERT INTO ttt1 (c1, c2, c3) VALUES (?, ?, ?)");
                            statement.setInt(1, i);
                            statement.setFloat(2, i);
                            statement.setString(3, valueOf(i));
                            statement.execute();
                            statement.close();
                        }
                    }
                });
            }
        });
    }

    @Test
    public void run2BatchInsert() {
        withinTimer("Batch insert", new Runnable() {
            @Override
            public void run() {
                runWithConnection(new IUnsafeIn<Connection, SQLException>() {
                    @Override
                    public void handle(Connection connection) throws SQLException {
                        final PreparedStatement statement = connection.prepareStatement("INSERT INTO ttt1 (c1, c2, c3) VALUES (?, ?, ?)");
                        for ( int i = 0; i < ITERATION_COUNT; i++ ) {
                            statement.setInt(1, i);
                            statement.setFloat(2, i);
                            statement.setString(3, valueOf(i));
                            statement.addBatch();
                        }
                        statement.executeBatch();
                        statement.close();
                    }
                });
            }
        });
    }

    @Test
    public void run3DirtyBulkInsert() {
        withinTimer("Dirty bulk insert", new Runnable() {
            @Override
            public void run() {
                runWithConnection(new IUnsafeIn<Connection, SQLException>() {
                    @Override
                    public void handle(Connection connection) throws SQLException {
                        final StringBuilder builder = new StringBuilder("INSERT INTO ttt1 (c1, c2, c3) VALUES ");
                        for ( int i = 0; i < ITERATION_COUNT; i++ ) {
                            if ( i != 0 ) {
                                builder.append(",");
                            }
                            builder.append(format("(%s, %s, '%s')", i, i, i));
                        }
                        final String query = builder.toString();
                        final PreparedStatement statement = connection.prepareStatement(query);
                        statement.execute();
                        statement.close();
                    }
                });
            }
        });
    }

    @Test
    public void run4SafeBulkInsert() {
        withinTimer("Safe bulk insert", new Runnable() {
            @Override
            public void run() {
                runWithConnection(new IUnsafeIn<Connection, SQLException>() {
                    private String getInsertPlaceholders(int placeholderCount) {
                        final StringBuilder builder = new StringBuilder("(");
                        for ( int i = 0; i < placeholderCount; i++ ) {
                            if ( i != 0 ) {
                                builder.append(",");
                            }
                            builder.append("?");
                        }
                        return builder.append(")").toString();
                    }

                    @SuppressWarnings("AssignmentToForLoopParameter")
                    @Override
                    public void handle(Connection connection) throws SQLException {
                        final int columnCount = 3;
                        final StringBuilder builder = new StringBuilder("INSERT INTO ttt1 (c1, c2, c3) VALUES ");
                        final String placeholders = getInsertPlaceholders(columnCount);
                        for ( int i = 0; i < ITERATION_COUNT; i++ ) {
                            if ( i != 0 ) {
                                builder.append(",");
                            }
                            builder.append(placeholders);
                        }
                        final int maxParameterIndex = ITERATION_COUNT * columnCount;
                        final String query = builder.toString();
                        final PreparedStatement statement = connection.prepareStatement(query);
                        int valueIndex = 0;
                        for ( int parameterIndex = 1; parameterIndex <= maxParameterIndex; valueIndex++ ) {
                            statement.setObject(parameterIndex++, valueIndex);
                            statement.setObject(parameterIndex++, valueIndex);
                            statement.setObject(parameterIndex++, valueIndex);
                        }
                        statement.execute();
                        statement.close();
                    }
                });
            }
        });
    }

}

Sehen Sie sich die mit der @Test-Annotation annotierten Methoden an:Sie führen tatsächlich den INSERT aus Aussagen. Bitte werfen Sie auch einen Blick auf CREATE_TABLE_QUERY Konstante:Im Quellcode wird InnoDB verwendet, was auf meinem Computer mit installiertem MySQL 5.5 (MySQL Connector/J 5.1.12) die folgenden Ergebnisse erzeugt:

InnoDB
Single inserts: 74148 ms
Batch insert: 84370 ms
Dirty bulk insert: 178 ms
Safe bulk insert: 118 ms

Wenn Sie CREATE_TABLE_QUERY ändern InnoDB zu MyISAM, würden Sie eine deutliche Leistungssteigerung feststellen:

MyISAM
Single inserts: 604 ms
Batch insert: 447 ms
Dirty bulk insert: 63 ms
Safe bulk insert: 26 ms

Hoffe das hilft.

UPDATE:

Für den vierten Weg müssen Sie das max_allowed_packet richtig anpassen in mysql.ini (die [mysqld] Abschnitt) groß genug sein, um wirklich große Pakete zu unterstützen.