sql >> Databasteknik >  >> RDS >> MariaDB

MariaDB Java Connector Driver Performance

MARIADB JAVA CONNECTOR PRESTANDA

Vi pratar alltid om prestation. Men saken är alltid "Mät, gissa inte!".

En hel del prestandaförbättringar har gjorts på senare tid på MariaDB Java Connector. Så, vad är den nuvarande förarens prestanda?

Låt mig dela ett benchmarkresultat av 3 jdbc-drivrutiner som tillåter åtkomst till en MySQL/MariaDB-databas: DrizzleJDBC, MySQL Connector/J och MariaDB java-anslutare.

Drivrutinsversionerna är den senaste tillgängliga versionen av GA när jag skriver den här bloggen:

  • MariaDB 1.5.3
  • MySQL 5.1.39
  • Drizzle 1.4

MÄKET

JMH är ett Oracle-ramverk för mikrobenchmarking utvecklat av Oracle, levererat som openJDK-verktyg, som kommer att vara den officiella Java 9-mikrobenchmark-sviten. Dess utmärkande fördel gentemot andra ramverk är att den är utvecklad av samma killar i Oracle som implementerar JIT (Just In Time-kompilering) och tillåter att undvika de flesta fallgropar i mikrobenchmark.

Benchmarkkälla: https://github.com/rusher/mariadb-java-driver-benchmark.

Tester är ganska enkla om du är bekant med java.
Exempel:

public class BenchmarkSelect1RowPrepareText utökar 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 utökar BenchmarkInit { private String request ="SELECT CAST(? som teckenuppsättning utf8)"; public String select1RowPrepare(Connection connection, MyState state) throws SQLException { try (PreparedStatement readyStatement =connection.prepareStatement(request)) { preparertStatement.setString(1, state.insertData[state.counter++]); try (ResultSet rs =preparertStatement.executeQuery()) { rs.next(); returnera rs.getString(1); } } }}

Tester som använder INSERTs frågor skickas till en BLACKHOLE-motor med den binära loggen inaktiverad, för att undvika IO och beroende av lagringsprestanda. Detta tillåter mer stabila resultat.
(Utan att använda blackhole-motorn och inaktivera binär logg, skulle körtiderna variera upp till 10%).

Benchmark har körts på databaser MariaDB Server 10.1.17 och MySQL Community Server 5.7.13. Följande dokument visar resultat med de 3 drivrutinerna med MariaDB Server 10.1.17. För de fullständiga resultaten inklusive de med MySQL Server 5.7.13, se länken längst ner i dokumentet.

MILJÖ

Exekvering (klient och server) görs på en enda serverdroppe på digitalocean.com med hjälp av följande parametrar:

  • Java(TM) SE Runtime Environment (build 1.8.0_101-b13) 64bitar (faktisk senaste version när man kör detta benchmark)
  • Ubuntu 16.04 64-bitars
  • 512 Mb minne
  • 1 CPU
  • databas MariaDB “10.1.17-MariaDB”, MySQL Community Server build “5.7.15-0ubuntu0.16.04.1”
    med standardkonfigurationsfiler och dessa ytterligare alternativ:

    • max_allowed_packet =40M #exchange-paket kan vara upp till 40mb
    • character-set-server =utf8 #för att använda UTF-8 som standard
    • sorteringsserver =utf8_unicode_ci #för att använda UTF-8 som standard

När det anges "avlägset", körs benchmarks med separat klient och server på 2 identiska värdar på samma datacenter med en genomsnittlig ping på 0,350 ms.

RESULTAT EXEMPEL FÖRKLARINGAR

Benchmark Score Error UnitsBenchmarkSelect1RowPrepareText.mariadb 62.715 ± 2.402 µs/opBenchmarkSelect1RowPrepareText.mysql 88.670 ± 3.505 µs/open 

Detta innebär att denna enkla fråga kommer att ta en genomsnittlig tid på 62,715 mikrosekunder med MariaDB-drivrutinen med en variation på ± 2,402 mikrosekunder för 99,9 % av frågorna.
Samma exekvering med duggregn kommer att ta en genomsnittlig tid på 88,670 mikrosekunder, och 78,672 mikrosekunder med MySQL-kontakt (kortare körtid desto bättre).

Visade procentsatser ställs in enligt mariadb första resultat som referens (100 %), vilket gör det möjligt att enkelt jämföra andra resultat.

PRESTANDAJÄMFÖRELSER

Benchmark kommer att testa prestandan för de tre huvudsakliga olika beteendena med samma lokala databas (samma server) och en avlägsen databas (en annan identisk server) på samma datacenter med en genomsnittlig ping på 0,450ms

Olika beteenden:

Textprotokoll

Detta motsvarar alternativet useServerPrepStmts inaktiverat.
Frågor skickas direkt till servern med utbyte av sanerade parametrar på klientsidan.
Data skickas som text. Exempel:En tidsstämpel kommer att skickas som texten "1970-01-01 00:00:00.000500" med 26 byte

Binärt protokoll

Detta motsvarar alternativet useServerPrepStmts aktiverat (standardimplementering på MariaDB-drivrutinen).
Data skickas binärt. Exempeltidsstämpel "1970-01-01 00:00:00.000500" kommer att skickas med 11 byte.

Det finns upp till 3 utbyten med servern för en fråga :

  1. PREPARE – Förbereder uttalande för exekvering.
  2. EXEKUT – Skicka parametrar
  3. DEALLOCATE PREPARE – släpper ett förberett uttalande.

Se dokumentationen för serverförberedelser för mer information.

PREPARE-resultat lagras i cache på förarsidan (standardstorlek 250). Om Prepare redan finns i cachen, kommer PREPARE inte att exekveras, DEALLOCATE exekveras endast när PREPARE inte används längre och inte i cachen. Det betyder att en viss frågekörning kommer att ha 3 tur och retur, men vissa kommer bara att ha en tur och retur och skickar en PREPARE identifierare och parametrar.

Skriv om

Detta motsvarar alternativet rewriteBatchedStatements aktiverat.
Rewrite använder textprotokollet och gäller endast batcher. Drivrutinen kommer att skriva om frågan för snabbare resultat.

Exempel:
Infoga i ab (i) värden (?) med första batchvärdena [1] och [2] kommer att skrivas om till
Infoga i ab (i) värden (1), (2).

Om frågan inte kan skrivas om i "multi-values", kommer rewrite att använda multi-queries:
Infoga i tabell(col1) värden (?) på dubblettnyckeluppdatering col2=? med värdena [1,2] och [2,3] kommer att skrivas om till
Infoga i tabell(col1) värden (1) på duplicate key update col2=2;Insert into table(col1) values ​​(3) on dubblettnyckeluppdatering col2=4

Nackdelar med detta alternativ är:

  • Automatisk inkrement-ID kan inte hämtas med Statement.html#getGeneratedKeys().
  • Flera frågor i en körning är aktiverade. Det är inte ett problem för PreparedStatement, men om applikationen använder Statement kan det vara en säkerhetsförsämring (SQL-injektion).

* MariaDB och MySQL har dessa tre beteenden implementerade, Drizzle endast textprotokollet.

RESULTAT från BENCHMARK

MariaDB-drivrutinsresultat

ENKEL VALFRÅGA

private String request ="SELECT CAST(? as Char character set utf8)";public String select1RowPrepare(Connection connection, MyState state) throws SQLException { try (PreparedStatement readyStatement =connection.prepareStatement(request)) { readyStatement.setString( 1, state.insertData[state.counter++]); //a slumpmässiga 100 byte. try (ResultSet rs =preparertStatement.executeQuery()) { rs.next(); returnera rs.getString(1); } }}
LOKAL DATABAS:BenchmarkSelect1RowPrepareHit.mariadb 58.267 ± 2.270 µs/opBenchmarkSelect1RowPrepareMiss.mariadb 118.896 ± 5.500 µs/opBenchmariT26/opBenchmarkSelect1b.ex.2 ±ProwprepareMiss.mariadb. 
DISTANT DATABASE:BenchmarkSelect1RowPrepareHit.mariadb 394.354 ± 13.102 µs/opBenchmarkSelect1RowPrepareMiss.mariadb 709.843 ± 31.090 µs/opBenchmark 

När PREPARE-resultatet för denna exakta fråga redan finns i cacheminnet (cacheträff), blir frågan snabbare (7,1 % i det här exemplet) än att använda textprotokoll. På grund av de ytterligare begäranden PREPARE och DEALLOCATE-utbytena är cachemissen 68,1 % långsammare.

Detta betonar fördelarna och olägenheterna med att använda ett binärt protokoll. Cache HIT är viktigt.

ENKEL INFOGA FRÅGA

private String request ="INSERT INTO blackholeTable (charValue) values ​​(?)";public boolean executeOneInsertPrepare(Connection connection, String[] datas) kastar SQLException { try (PreparedStatement prepareradeStatement =connection.prepareStatement(request)) { readyStatement. setString(1, data[0]); //en slumpmässig 100 byte dataretur prepareradeStatement.execute(); }}
LOKAL DATABAS:BenchmarkOneInsertPrepareHit.mariadb 61.298 ± 1.940 µs/opBenchmarkOneInsertPrepareMiss.mariadb 130.896 ± 6.362 µs/opBenchmariOneµs/opBenchmariOneµs.8b 
DISTANT DATABASE:BenchmarkOneInsertPrepareHit.mariadb 379.295 ± 17.351 µs/opBenchmarkOneInsertPrepareMiss.mariadb 802.287 ± 24.825 µs/opBenchmark 

Resultaten för INSERTs liknar SELECTs resultat.

BATCH:1000 INFOGA FRÅGA

private String request ="INSERT INTO blackholeTable (charValue) values ​​(?)";public int[] executeBatch(Connection connection, String[] data) throws SQLException { try (PreparedStatement readyStatement =connection.prepareStatement(request)) { for (int i =0; i <1000; i++) { preparertStatement.setString(1, data[i]); //en slumpmässig 100 byte data prepareradStatement.addBatch(); } returnera prepareradeStatement.executeBatch(); }}
LOKAL DATABAS:PrepareStatementBatch100InsertPrepareHit.mariadb 5,290 ± 0,232 ms/opPrepareStatementBatch100InsertRewrite.mariadb 0,404 ± 0,014 ms/opPrepare100ement 1 
DISTANT DATABAS:PrepareStatementBatch100InsertPrepareHit.mariadb 7.639 ± 0.476 ms/opPrepareStatementBatch100InsertRewrite.mariadb 1.164 ± 0.037 ms/opPreparch100ement 8.4 

Att använda binärt protokoll är här viktigare, med resultat 13 % snabbare än att använda textprotokoll.

Inlägg skickas i bulk och resultaten läses asynkront (vilket motsvarar optionuseBatchMultiSend). Detta tillåter att få avlägsna resultat med prestanda inte långt från de lokala.

Rewrite har fantastiskt bra prestanda, men kommer inte att ha auto-increment id. Om du inte behöver ID omedelbart och inte använder ORM kommer den här lösningen att vara den snabbaste. Vissa ORM tillåter konfiguration för att hantera sekvens internt för att tillhandahålla inkrement-ID, men dessa sekvenser distribueras inte, så de fungerar inte på kluster.

JÄMFÖRELSE MED ANDRA DRIVRUTARE

VÄLJ fråga med resultat på en rad

BenchmarkSelect1RowPrepareHit.mariadb 58.267 ± 2.270 µs/opBenchmarkSelect1RowPrepareHit.mysql 73.789 ± 1.863 µs/opBenchmarkSelect1RowPrepareMiss.mariadb 118.896 ± 5.500 µs/opBenchmarkSelect1RowPrepareMiss.mysql 150.679 ± 4.791 µs/opBenchmarkSelect1RowPrepareText.mariadb 62.715 ± 2.402 µs/opBenchmarkSelect1RowPrepareText.mysql 88.670 ± 3.505 µs /opBenchmarkSelect1RowPrepareText.drizzle 78.672 ± 2.971 µs/opBenchmarkSelect1RowPrepareTextHA.mariadb 64.676 ± 2.192 µs/opBenchmarkSelect1RowPrepareText .8±prepareText 

HA står för "High Availability" med hjälp av Master-Slave-konfigurationen
(anslutnings-URL är "jdbc:mysql:replication://localhost:3306,localhost:3306/testj").

Dessa resultat beror på många olika implementeringsval. Här är några skäl som förklarar tidsskillnader:

  • MariaDB-drivrutinen är optimerad för UTF-8, vilket tillåter mindre skapande av byte-array, undviker array-kopiering och minnesförbrukning.
  • HA-implementering:MariaDB- och MySQL-drivrutiner använder en dynamisk proxyklass i Java som sitter mellan Statement-objekt och sockets, vilket gör det möjligt att lägga till failover-beteende. Dessa tillägg kommer att kosta en overhead på 2 mikrosekunder per fråga (62,715 utan att bli 64,676 mikrosekunder).
    I MySQL-implementering har nästan alla interna metoder proxy, vilket lägger till en overhead för massor av metoder som inte har något att göra med failover, vilket lägger till en total overhead på 50 mikrosekunder för varje fråga.

(Drizzle har ingen PREPARE, inte heller HA-funktionalitet)

"Välj 1000 rader"

private String request ="välj * från seq_1_to_1000"; //användning av sekvenslagringsmotornprivate ResultSet select1000Row(Connection connection) kastar SQLException { try (Statement statement =connection.createStatement()) { try (ResultSet rs =statement.executeQuery(request)) { while (rs.next()) { rs.getString(1); } returnera rs; } }
BenchmarkSelect1000Rows.mariadb 244,228 ± 7,686 µs/opBenchmarkSelect1000Rows.mysql 298,814 ± 12,143 µs/opBenchmarkSelect1000Rows.µ100Rows. 

När man använder mycket data läggs tiden mest på att läsa från uttaget och lagra resultatet i minnet för att skicka det tillbaka till klienten. Om riktmärket bara körde SELECT utan att läsa resultaten, skulle körtiden för MySQL och MariaDB vara likvärdig. Eftersom målet med en SELECT-fråga är att få resultat, är MariaDB-drivrutinen optimerad för att ge tillbaka resultat (undviker att skapa byte-arrayer).

“Infoga 1000 rader”

LOCAL DATABASE:PrepareStatementBatch100InsertPrepareHit.mariadb 5.290 ± 0.232 ms/opPrepareStatementBatch100InsertPrepareHit.mysql 9.015 ± 0.440 ms/opPrepareStatementBatch100InsertRewrite.mariadb 0.404 ± 0.014 ms/opPrepareStatementBatch100InsertRewrite.mysql 0.592 ± 0.016 ms/opPrepareStatementBatch100InsertText.mariadb 6.081 ± 0.254 ms/opPrepareStatementBatch100InsertText.mysql 7.932 ± 0,293 ms/opPrepareStatementBatch100InsertText.drizzle 7,314 ± 0,205 ms/op
DISTANT DATABASE:PrepareStatementBatch100InsertPrepareHit.mariadb 7.639 ± 0.476 ms/opPrepareStatementBatch100InsertPrepareHit.mysql 43.636 ± 1.408 ms/opPrepareStatementBatch100InsertRewrite.mariadb 1.164 ± 0.037 ms/opPrepareStatementBatch100InsertRewrite.mysql 1.432 ± 0.050 ms/opPrepareStatementBatch100InsertText.mariadb 8.148 ± 0.563 ms/opPrepareStatementBatch100InsertText.mysql 43.804 ± 1,417 ms/opPrepareStatementBatch100InsertText.drizzle 38,735 ± 1,731 ms/op

MySQL och Drizzle bulk insert är som X INSERT:Drivrutinen skickar 1 INSERT, vänta på infogningsresultat och skicka nästa insert. Nätverkslatensen mellan varje infogning kommer att sakta ner infogningen.

Butiksrutiner

PROCEDUR SAMTAL

//SKAPA PROCEDUR inoutParam(INOUT p1 INT) start set p1 =p1 + 1; endprivate String request ="{call inOutParam(?)}";private String callableStatementWithOutParameter(Connection connection, MyState state) kastar SQLException { try (CallableStatement storedProc =connection.prepareCall(request)) { storedProc.setInt(1).functionVar1; //2 storedProc.registerOutParameter(1, Types.INTEGER); storedProc.execute(); return storedProc.getString(1); }}
BenchmarkCallableStatementWithOutParameter.mariadb 88.572 ± 4.263 µs/opBenchmarkCallableStatementWithOutParameter.mysql 714.108 ± 44.390 µs/op

MySQL- och MariaDB-implementeringarna skiljer sig helt åt. Mysql-drivrutinen kommer att använda många dolda frågor för att få utdataresultat:

  • SHOW CREATE PROCEDURE testj.inoutParam för att identifiera IN- och UT-parametrar
  • SET @com_mysql_jdbc_outparam_p1 = 1 för att skicka data enligt IN / OUT-parametrar
  • CALL testj.inoutParam(@com_mysql_jdbc_outparam_p1) anropsförfarande
  • SELECT @com_mysql_jdbc_outparam_p1 för att läsa resultatet

MariaDB-implementeringen är enkel med en möjlighet att ha OUT-parameter i serversvaret utan några ytterligare frågor. (Det är huvudorsaken till att MariaDB-drivrutinen kräver MariaDB/MySQL-serverversion 5.5.3 eller senare).

SLUTSATS

MariaDB-föraren rockar!

Det binära protokollet har olika fördelar men förlitar sig på att ha PREPARE-resultaten redan i cachen. Om applikationer har många olika typer av frågor och databasen är avlägsen, kanske det inte är den bättre lösningen.

Rewrite har fantastiska resultat för att skriva data i batch

Föraren håller bra jämfört med andra förare. Och det finns mycket att komma, men det är en annan historia.

Råresultat:

  1. med en MariaDB 10.1.17 databas lokal, avlägsen
  2. med en MySQL Community Server 5.7.15-databas (byggd 5.7.15-0ubuntu0.16.04.1) lokal

  1. Hur RPAD() fungerar i MariaDB

  2. MariaDB JSON_QUOTE() Förklarad

  3. Orsakas av:java.lang.NoSuchMethodError:org.postgresql.core.BaseConnection.getEncoding()Lorg/postgresql/core/Encoding;

  4. Varför återvänder pg_restore framgångsrikt men återställer inte min databas?