sql >> Database >  >> RDS >> Mysql

Prestaties van MySQL Insert-statements in Java:Batch-modus voorbereide statements versus enkelvoudig invoegen met meerdere waarden

JDBC is gewoon een Java SE-standaard voor databasetoegang die de standaardinterfaces biedt, dus u bent niet echt gebonden aan een specifieke JDBC-implementatie. MySQL Java-connector (Connector/J) is een implementatie van de JDBC-interfaces voor alleen MySQL-databases. Uit ervaring ben ik betrokken bij een project dat enorme hoeveelheden gegevens gebruikt met behulp van MySQL, en we geven meestal de voorkeur aan MyISAM voor gegevens die kunnen worden gegenereerd:het maakt het mogelijk om veel hogere prestaties te bereiken en transacties te verliezen, maar over het algemeen is MyISAM sneller, maar InnoDB is betrouwbaarder.

Ik vroeg me ongeveer een jaar geleden ook af hoe goed de INSERT-statements waren, en vond de volgende oude testcode in mijn codeplank (sorry, het is een beetje ingewikkeld en valt een beetje buiten je vraagkader). De onderstaande code bevat voorbeelden van 4 manieren om de testgegevens in te voegen:

  • enkel INSERT s;
  • gegroepeerd INSERT s;
  • handmatig bulken INSERT (gebruik het nooit - het is gevaarlijk);
  • en ten slotte massaal voorbereid INSERT ).

Het gebruikt TestNG als de hardloper, en gebruikt een aantal aangepaste code-erfenis zoals:

  • de runWithConnection() methode - zorgt ervoor dat de verbinding wordt gesloten of teruggezet naar de verbindingspool nadat de callback is uitgevoerd (maar de onderstaande code gebruikt geen betrouwbare strategie voor het sluiten van de instructie - zelfs zonder try /finally om de code te verkleinen);
  • IUnsafeIn<T, E extends Throwable> - een aangepaste callback-interface voor de methoden die een enkele parameter accepteren maar mogelijk een uitzondering van type E genereren, zoals: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();
                    }
                });
            }
        });
    }

}

Bekijk de methoden die zijn geannoteerd met de @Test-annotatie:ze voeren de INSERT daadwerkelijk uit verklaringen. Bekijk ook de CREATE_TABLE_QUERY constant:in de broncode gebruikt het InnoDB en produceert het de volgende resultaten op mijn computer met MySQL 5.5 geïnstalleerd (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

Als u de CREATE_TABLE_QUERY . wijzigt InnoDB naar MyISAM, je zou een aanzienlijke prestatieverbetering zien:

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

Ik hoop dat dit helpt.

UPD:

Voor de 4e manier moet je het max_allowed_packet correct aanpassen in mysql.ini (de [mysqld] sectie) om groot genoeg te zijn om echt grote pakketten te ondersteunen.



  1. Gesplitste functie-equivalent in T-SQL?

  2. uren berekenen op basis van kantooruren in Oracle SQL

  3. MySQL-prestaties:enkele tafel of meerdere tabellen

  4. Wat is het verschil tussen expliciete en impliciete cursors in Oracle?