Mysql
 sql >> Baza danych >  >> RDS >> Mysql

Wydajność instrukcji MySQL Insert w Javie:przygotowane instrukcje w trybie wsadowym vs pojedyncze wstawianie z wieloma wartościami

JDBC to po prostu standard dostępu do baz danych Java SE oferujący standardowe interfejsy, więc nie jesteś tak naprawdę związany z konkretną implementacją JDBC. MySQL Java Connector (Connector/J) to implementacja interfejsów JDBC tylko dla baz danych MySQL. Z doświadczenia jestem zaangażowany w projekt, który wykorzystuje ogromną ilość danych przy użyciu MySQL, a do danych, które można wygenerować, preferujemy przede wszystkim MyISAM:pozwala to na osiągnięcie znacznie większej wydajności przy traconych transakcjach, ale ogólnie rzecz biorąc MyISAM jest szybszy, ale InnoDB jest bardziej niezawodny.

Zastanawiałem się też nad wydajnością instrukcji INSERT około rok temu i znalazłem następujący stary kod testowy w mojej półce z kodem (przepraszam, jest to trochę skomplikowane i trochę poza zakresem twojego pytania). Poniższy kod zawiera przykłady 4 sposobów wstawiania danych testowych:

  • pojedynczy INSERT s;
  • partiami INSERT s;
  • ręcznie zbiorcze INSERT (nigdy go nie używaj - to niebezpieczne);
  • i wreszcie przygotowane zbiorczo INSERT ).

Używa TestNG jako biegacz i używa starszego kodu niestandardowego, takiego jak:

  • runWithConnection() metoda - zapewnia zamknięcie połączenia lub powrót do puli połączeń po wykonaniu wywołania zwrotnego (ale poniższy kod używa mało niezawodnej strategii zamykania instrukcji - nawet bez try /finally aby zredukować kod);
  • IUnsafeIn<T, E extends Throwable> - niestandardowy interfejs wywołania zwrotnego dla metod akceptujących pojedynczy parametr, ale potencjalnie zgłaszających wyjątek typu E, np.: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();
                    }
                });
            }
        });
    }

}

Spójrz na metody opisane adnotacją @Test:faktycznie wykonują one INSERT sprawozdania. Spójrz także na CREATE_TABLE_QUERY stała:w kodzie źródłowym używa InnoDB dając następujące wyniki na moim komputerze z zainstalowanym MySQL 5.5 (MySQL Connector/J 5.1.12):

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

Jeśli zmienisz CREATE_TABLE_QUERY InnoDB do MyISAM, zobaczysz znaczny wzrost wydajności:

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

Mam nadzieję, że to pomoże.

UPD:

W czwartym sposobie musisz odpowiednio dostosować max_allowed_packet w mysql.ini ([mysqld] sekcji), aby był wystarczająco duży, aby obsłużyć naprawdę duże pakiety.



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak obliczyć procentowy wzrost miesiąc po miesiącu w MySQL?

  2. Używanie przygotowanych instrukcji w C# z Mysql

  3. MySQL GDZIE W ()

  4. Sumuj wiersze z różnych warunków w Mysql

  5. Składnia wyzwalacza MySQL „aktualizacja w kolumnie”