sql >> Database >  >> RDS >> MariaDB

Prestaties van MariaDB Java Connector-stuurprogramma

PRESTATIES MARIADB JAVA-CONNECTOR

We hebben het altijd over prestaties. Maar het ding is altijd "Meet, raad niet!".

Er is de laatste tijd veel prestatieverbetering gedaan op de MariaDB Java Connector. Dus, wat zijn de huidige prestaties van de bestuurder?

Laat me een benchmarkresultaat delen van 3 jdbc-stuurprogramma's die toegang geven tot een MySQL/MariaDB-database:DrizzleJDBC, MySQL Connector/J en MariaDB Java-connector.

De versies van de stuurprogramma's zijn de nieuwste beschikbare GA-versie op het moment van schrijven van deze blog:

  • MariaDB 1.5.3
  • MySQL 5.1.39
  • Motregen 1.4

DE BENCHMARK

JMH is een Oracle microbenchmarking framework-tool ontwikkeld door Oracle, geleverd als openJDK-tools, die de officiële java 9 microbenchmark-suite zal zijn. Het onderscheidende voordeel ten opzichte van andere frameworks is dat het is ontwikkeld door dezelfde jongens in Oracle die JIT (Just In Time-compilatie) implementeren en toestaan ​​om de meeste valkuilen van microbenchmarks te vermijden.

Benchmarkbron: https://github.com/rusher/mariadb-java-driver-benchmark.

Testen zijn vrij eenvoudig als u bekend bent met Java.
Voorbeeld:

public class BenchmarkSelect1RowPrepareText extends BenchmarkSelect1RowPrepareAbstract {

    @Benchmark
    public String mysql(MyState state) throws Throwable {
        return select1RowPrepare(state.mysqlConnectionText, state);
    }

    @Benchmark
    public String mariadb(MyState state) throws Throwable {
        return select1RowPrepare(state.mariadbConnectionText, state);
    }
  
    @Benchmark
    public String drizzle(MyState state) throws Throwable {
        return select1RowPrepare(state.drizzleConnectionText, state);
    }
  
}

public abstract class BenchmarkSelect1RowPrepareAbstract extends BenchmarkInit {
    private String request = "SELECT CAST(? as char character set utf8)";

    public String select1RowPrepare(Connection connection, MyState state) throws SQLException {
        try (PreparedStatement preparedStatement = connection.prepareStatement(request)) {
            preparedStatement.setString(1, state.insertData[state.counter++]);
            try (ResultSet rs = preparedStatement.executeQuery()) {
                rs.next();
                return rs.getString(1);
            }
        }
    }
}

Tests met INSERT-query's worden verzonden naar een BLACKHOLE-engine met het binaire logboek uitgeschakeld, om IO en afhankelijkheid van de opslagprestaties te voorkomen. Dit geeft stabielere resultaten.
(Zonder gebruik van de blackhole-engine en het uitschakelen van het binaire logboek, zouden de uitvoeringstijden tot 10% variëren).

Benchmark is uitgevoerd op MariaDB Server 10.1.17 en MySQL Community Server 5.7.13 databases. Het volgende document toont resultaten met behulp van de 3 stuurprogramma's met MariaDB Server 10.1.17. Zie de link onderaan het document voor de volledige resultaten, inclusief die met MySQL Server 5.7.13.

MILIEU

Uitvoering (client en server) gebeurt op een enkele serverdruppel op digitalocean.com met behulp van de volgende parameters:

  • Java(TM) SE Runtime Environment (build 1.8.0_101-b13) 64 bits (werkelijke laatste versie bij het uitvoeren van deze benchmark)
  • Ubuntu 16.04 64 bits
  • 512 MB geheugen
  • 1 CPU
  • database MariaDB "10.1.17-MariaDB", MySQL Community Server build "5.7.15-0ubuntu0.16.04.1"
    met standaardconfiguratiebestanden en deze extra opties:

    • max_allowed_packet =40M #uitwisselingspakket kan tot 40mb zijn
    • character-set-server =utf8 #om UTF-8 als standaard te gebruiken
    • collation-server =utf8_unicode_ci #om UTF-8 als standaard te gebruiken

Wanneer "ver" wordt aangegeven, worden benchmarks uitgevoerd met afzonderlijke client en server op 2 identieke hosts in hetzelfde datacenter met een gemiddelde ping van 0,350 ms.

RESULTATEN VOORBEELD UITLEG

Benchmark                                           Score     Error  Units
BenchmarkSelect1RowPrepareText.mariadb              62.715 ±  2.402  µs/op
BenchmarkSelect1RowPrepareText.mysql                88.670 ±  3.505  µs/op
BenchmarkSelect1RowPrepareText.drizzle              78.672 ±  2.971  µs/op

Dit betekent dat deze eenvoudige query gemiddeld 62,715 microseconden in beslag neemt met het MariaDB-stuurprogramma met een variatie van ± 2,402 microseconden voor 99,9% van de vragen.
Dezelfde uitvoering met behulp van de drizzle-driver duurt gemiddeld 88,670 microseconden, en 78.672 microseconden bij gebruik van MySQL-connector (kortere uitvoeringstijd, hoe beter).

De weergegeven percentages worden ingesteld op basis van het eerste mariadb-resultaat als referentie (100%), waardoor andere resultaten gemakkelijk kunnen worden vergeleken.

PRESTATIEVERGELIJKINGEN

De benchmark test de prestaties van de 3 belangrijkste verschillende gedragingen met behulp van dezelfde lokale database (dezelfde server) en een verre database (een andere identieke server) op hetzelfde datacenter met een gemiddelde ping van 0,450 ms

Ander gedrag:

Tekstprotocol

Dit komt overeen met de optie useServerPrepStmts uitgeschakeld.
Query's worden rechtstreeks naar de server gestuurd, waarbij de opgeschoonde parameters worden vervangen aan de clientzijde.
Gegevens worden als tekst verzonden. Voorbeeld:een tijdstempel wordt verzonden als tekst "1970-01-01 00:00:00.000500" met 26 bytes

Binair protocol

Dit komt overeen met de optie useServerPrepStmts ingeschakeld (standaardimplementatie op MariaDB-stuurprogramma).
Gegevens worden binair verzonden. Voorbeeld tijdstempel "1970-01-01 00:00:00.000500" wordt verzonden met 11 bytes.

Er zijn tot 3 uitwisselingen met de server voor één vraag:

  1. PREPARE – Bereidt de verklaring voor uitvoering voor.
  2. UITVOEREN – Parameters verzenden
  3. DEALLOCATE PREPARE – Geeft een voorbereide verklaring uit.

Zie de documentatie voor het voorbereiden van de server voor meer informatie.

PREPARE-resultaten worden opgeslagen in de cache aan de bestuurderszijde (standaardgrootte 250). Als Prepare al in de cache is, wordt PREPARE niet uitgevoerd, DEALLOCATE wordt alleen uitgevoerd als PREPARE niet meer wordt gebruikt en niet in de cache. Dat betekent dat sommige query-uitvoeringen 3 retourvluchten zullen hebben, maar sommige slechts één retourvlucht, waarbij een PREPARE-ID en parameters worden verzonden.

Herschrijven

Dit komt overeen met de optie rewriteBatchedStatements ingeschakeld.
Rewrite gebruikt het tekstprotocol en betreft alleen batches. Het stuurprogramma zal de zoekopdracht herschrijven voor snellere resultaten.

Voorbeeld:
Invoegen in ab (i) waarden (?) waarbij de eerste batchwaarden [1] en [2] worden herschreven in
Invoegen in ab (i) waarden (1), (2).

Als de query niet kan worden herschreven in "multi-waarden", zal herschrijven gebruik maken van meerdere query's:
Invoegen in tabel (col1) waarden (?) op dubbele sleutel update col2=? met waarden [1,2] en [2,3] worden herschreven tot
Invoegen in tabel(col1) waarden (1) op dubbele sleutel update col2=2;Invoegen in tabel(col1) waarden (3) op dubbele sleutel update col2=4

Nadelen van deze optie zijn:

  • ID's voor automatische verhoging kunnen niet worden opgehaald metStatement.html#getGeneratedKeys().
  • Meerdere zoekopdrachten in één uitvoering zijn ingeschakeld. Dat is geen probleem voorPreparedStatement, maar als de app Statement gebruikt, kan dat een verslechtering van de beveiliging zijn (SQL-injectie).

* MariaDB en MySQL hebben die 3 gedragingen geïmplementeerd, besprenkel alleen het tekstprotocol.

BENCHMARK-RESULTATEN

MariaDB-stuurprogrammaresultaten

ENKELE SELECTIE QUERY

private String request = "SELECT CAST(? as char character set utf8)";

public String select1RowPrepare(Connection connection, MyState state) throws SQLException {
    try (PreparedStatement preparedStatement = connection.prepareStatement(request)) {
        preparedStatement.setString(1, state.insertData[state.counter++]); //a random 100 bytes.
        try (ResultSet rs = preparedStatement.executeQuery()) {
            rs.next();
            return rs.getString(1);
        }
    }
}
LOCAL DATABASE:
BenchmarkSelect1RowPrepareHit.mariadb               58.267 ±  2.270  µs/op
BenchmarkSelect1RowPrepareMiss.mariadb             118.896 ±  5.500  µs/op
BenchmarkSelect1RowPrepareText.mariadb              62.715 ±  2.402  µs/op
DISTANT DATABASE:
BenchmarkSelect1RowPrepareHit.mariadb               394.354 ±  13.102  µs/op
BenchmarkSelect1RowPrepareMiss.mariadb              709.843 ±  31.090  µs/op
BenchmarkSelect1RowPrepareText.mariadb              422.215 ±  15.858  µs/op

Wanneer het PREPARE-resultaat voor deze exacte zoekopdracht al in de cache staat (cachetreffer), zal de zoekopdracht sneller zijn (7,1% in dit voorbeeld) dan bij het gebruik van tekstprotocol. Vanwege het extra verzoek om PREPARE- en DEALLOCATE-uitwisselingen te doen, is cachemisser 68,1% langzamer.

Dit benadrukt de voor- en nadelen van het gebruik van een binair protocol. Cache HIT is belangrijk.

EEN VRAAG INVOEREN

private String request = "INSERT INTO blackholeTable (charValue) values (?)";

public boolean executeOneInsertPrepare(Connection connection, String[] datas) throws SQLException {
    try (PreparedStatement preparedStatement = connection.prepareStatement(request)) {
        preparedStatement.setString(1, datas[0]); //a random 100 byte data
        return preparedStatement.execute();
    }
}
LOCAL DATABASE:
BenchmarkOneInsertPrepareHit.mariadb                 61.298 ±  1.940  µs/op
BenchmarkOneInsertPrepareMiss.mariadb               130.896 ±  6.362  µs/op
BenchmarkOneInsertPrepareText.mariadb                68.363 ±  2.686  µs/op
DISTANT DATABASE:
BenchmarkOneInsertPrepareHit.mariadb                379.295 ±  17.351  µs/op
BenchmarkOneInsertPrepareMiss.mariadb               802.287 ±  24.825  µs/op
BenchmarkOneInsertPrepareText.mariadb               415.125 ±  14.547  µs/op

Resultaten voor INSERT's zijn vergelijkbaar met de resultaten van SELECT.

BATCH:1000 VRAAG INVOEREN

private String request = "INSERT INTO blackholeTable (charValue) values (?)";

public int[] executeBatch(Connection connection, String[] data) throws SQLException {
  try (PreparedStatement preparedStatement = connection.prepareStatement(request)) {
    for (int i = 0; i < 1000; i++) {
      preparedStatement.setString(1, data[i]); //a random 100 byte data
      preparedStatement.addBatch();
    }
    return preparedStatement.executeBatch();
  }
}
LOCAL DATABASE:        
PrepareStatementBatch100InsertPrepareHit.mariadb    5.290 ±  0.232  ms/op
PrepareStatementBatch100InsertRewrite.mariadb       0.404 ±  0.014  ms/op
PrepareStatementBatch100InsertText.mariadb          6.081 ±  0.254  ms/op
DISTANT DATABASE:        
PrepareStatementBatch100InsertPrepareHit.mariadb    7.639 ±   0.476  ms/op
PrepareStatementBatch100InsertRewrite.mariadb       1.164 ±   0.037  ms/op
PrepareStatementBatch100InsertText.mariadb          8.148 ±   0.563  ms/op

Het gebruik van een binair protocol is hier belangrijker, met resultaten die 13% sneller zijn dan het gebruik van een tekstprotocol.

Bijlagen worden in bulk verzonden en de resultaten worden asynchroon gelezen (dat komt overeen met de optie om BatchMultiSend te gebruiken). Deze toestemming om verre resultaten te hebben met prestaties niet ver van die lokale.

Rewrite heeft verbazingwekkend goede prestaties, maar heeft geen auto-increment-id's. Als je id's niet meteen nodig hebt en geen ORM gebruikt, is deze oplossing de snelste. Sommige ORM's staan ​​configuratie toe om reeksen intern af te handelen om increment-id's te leveren, maar die reeksen worden niet gedistribueerd en werken dus niet op clusters.

VERGELIJKING MET ANDERE BESTUURDERS

SELECT-query met resultaat van één rij

BenchmarkSelect1RowPrepareHit.mariadb                58.267 ±  2.270  µs/op
BenchmarkSelect1RowPrepareHit.mysql                  73.789 ±  1.863  µs/op
BenchmarkSelect1RowPrepareMiss.mariadb              118.896 ±  5.500  µs/op
BenchmarkSelect1RowPrepareMiss.mysql                150.679 ±  4.791  µs/op
BenchmarkSelect1RowPrepareText.mariadb               62.715 ±  2.402  µs/op
BenchmarkSelect1RowPrepareText.mysql                 88.670 ±  3.505  µs/op
BenchmarkSelect1RowPrepareText.drizzle               78.672 ±  2.971  µs/op
BenchmarkSelect1RowPrepareTextHA.mariadb             64.676 ±  2.192  µs/op
BenchmarkSelect1RowPrepareTextHA.mysql              137.289 ±  4.872  µs/op

HA staat voor "High Availability" bij gebruik van de Master-Slave-configuratie
(verbindings-URL is "jdbc:mysql:replication://localhost:3306,localhost:3306/testj").

Deze resultaten zijn te wijten aan veel verschillende implementatiekeuzes. Hier zijn enkele redenen die tijdsverschillen verklaren:

  • MariaDB-stuurprogramma is geoptimaliseerd voor UTF-8, waardoor minder bytes-array hoeft te worden gemaakt, array-kopie en geheugengebruik worden vermeden.
  • HA-implementatie:MariaDB- en MySQL-stuurprogramma's gebruiken een dynamische java-proxyklasse tussen Statement-objecten en sockets, waardoor failover-gedrag kan worden toegevoegd. Die toevoeging kost een overhead van 2 microseconden per query (62,715 zonder 64,676 microseconden te worden).
    In de MySQL-implementatie worden bijna alle interne methoden via proxy's gebruikt, wat een overhead toevoegt voor veel methoden die niets met failover te maken hebben. een totale overhead van 50 microseconden voor elke zoekopdracht.

(Drizzle heeft geen PREPARE, noch HA-functionaliteit)

“Selecteer 1000 rijen”

private String request = "select * from seq_1_to_1000"; //using the sequence storage engine

private ResultSet select1000Row(Connection connection) throws SQLException {
  try (Statement statement = connection.createStatement()) {
    try (ResultSet rs = statement.executeQuery(request)) {
      while (rs.next()) {
        rs.getString(1);
      }
      return rs;
    }
  }
BenchmarkSelect1000Rows.mariadb                     244.228 ±  7.686  µs/op
BenchmarkSelect1000Rows.mysql                       298.814 ± 12.143  µs/op
BenchmarkSelect1000Rows.drizzle                     406.877 ± 16.585  µs/op

Bij het gebruik van veel gegevens wordt de meeste tijd besteed aan het lezen van de socket en het opslaan van het resultaat in het geheugen om het terug te sturen naar de klant. Als de benchmark alleen de SELECT uitvoerde zonder de resultaten te lezen, zou de uitvoeringstijd van MySQL en MariaDB gelijk zijn. Aangezien het doel van een SELECT-query is om resultaten te hebben, is het MariaDB-stuurprogramma geoptimaliseerd om resultaten terug te geven (waardoor het maken van bytes-arrays wordt vermeden).

'1000 rijen invoegen'

LOCAL DATABASE:        
PrepareStatementBatch100InsertPrepareHit.mariadb    5.290 ±  0.232  ms/op
PrepareStatementBatch100InsertPrepareHit.mysql      9.015 ±  0.440  ms/op
PrepareStatementBatch100InsertRewrite.mariadb       0.404 ±  0.014  ms/op
PrepareStatementBatch100InsertRewrite.mysql         0.592 ±  0.016  ms/op
PrepareStatementBatch100InsertText.mariadb          6.081 ±  0.254  ms/op
PrepareStatementBatch100InsertText.mysql            7.932 ±  0.293  ms/op
PrepareStatementBatch100InsertText.drizzle          7.314 ±  0.205  ms/op
DISTANT DATABASE:        
PrepareStatementBatch100InsertPrepareHit.mariadb     7.639 ±   0.476  ms/op
PrepareStatementBatch100InsertPrepareHit.mysql      43.636 ±   1.408  ms/op
PrepareStatementBatch100InsertRewrite.mariadb        1.164 ±   0.037  ms/op
PrepareStatementBatch100InsertRewrite.mysql          1.432 ±   0.050  ms/op
PrepareStatementBatch100InsertText.mariadb           8.148 ±   0.563  ms/op
PrepareStatementBatch100InsertText.mysql            43.804 ±   1.417  ms/op
PrepareStatementBatch100InsertText.drizzle          38.735 ±   1.731  ms/op

MySQL en Drizzle bulk insert zijn zoals X INSERT's:Driver verzendt 1 INSERT, wacht op het resultaat van de insert en verzend de volgende insert. De netwerklatentie tussen elke insertie vertraagt ​​de inserties.

Bewaarprocedures

PROCEDURE OPROEP

//CREATE PROCEDURE inoutParam(INOUT p1 INT) begin set p1 = p1 + 1; end
private String request = "{call inOutParam(?)}";

private String callableStatementWithOutParameter(Connection connection, MyState state) 
		throws SQLException {
  try (CallableStatement storedProc = connection.prepareCall(request)) {
    storedProc.setInt(1, state.functionVar1); //2
    storedProc.registerOutParameter(1, Types.INTEGER);
    storedProc.execute();
    return storedProc.getString(1);
  }
}
BenchmarkCallableStatementWithOutParameter.mariadb   88.572 ±  4.263  µs/op
BenchmarkCallableStatementWithOutParameter.mysql    714.108 ± 44.390  µs/op

MySQL- en MariaDB-implementaties verschillen volledig. Mysql-stuurprogramma zal veel verborgen zoekopdrachten gebruiken om uitvoerresultaten te verkrijgen:

  • SHOW CREATE PROCEDURE testj.inoutParam om IN- en OUT-parameters te identificeren
  • SET @com_mysql_jdbc_outparam_p1 = 1 om gegevens te verzenden volgens IN / OUT-parameters
  • CALL testj.inoutParam(@com_mysql_jdbc_outparam_p1) oproepprocedure
  • SELECT @com_mysql_jdbc_outparam_p1 output resultaat lezen

MariaDB-implementatie is eenvoudig met de mogelijkheid om de OUT-parameter in het serverantwoord te hebben zonder aanvullende vragen. (Dat is de belangrijkste reden waarom het MariaDB-stuurprogramma MariaDB/MySQL-serverversie 5.5.3 of hoger vereist).

CONCLUSIE

MariaDB-stuurprogramma rockt!

Het binaire protocol heeft verschillende voordelen, maar is afhankelijk van het feit dat de PREPARE-resultaten al in de cache staan. Als applicaties veel verschillende soorten zoekopdrachten hebben en de database ver verwijderd is, is dat misschien niet de betere oplossing.

Herschrijven heeft verbluffende resultaten om gegevens in batch te schrijven

Bestuurder houdt goed ten opzichte van andere bestuurders. En er staat nog veel op stapel, maar dat is een ander verhaal.

Ruwe resultaten:

  1. met een MariaDB 10.1.17-database lokaal, op afstand
  2. met een MySQL Community Server 5.7.15-database (build 5.7.15-0ubuntu0.16.04.1) lokaal

  1. Tabelnaam als een PostgreSQL-functieparameter

  2. Hoe AT TIME ZONE werkt in PostgreSQL

  3. FOUT:er is geen unieke beperking die overeenkomt met de gegeven sleutels voor de tabelbalk waarnaar wordt verwezen

  4. Een web-app maken vanaf nul met Python Flask en MySQL:deel 3