sql >> Database teknologi >  >> RDS >> MariaDB

MariaDB Java Connector Driver Ydeevne

MARIADB JAVA CONNECTOR YDELSE

Vi taler altid om præstation. Men sagen er altid "Mål, gæt ikke!".

En masse forbedringer af ydeevnen er blevet gjort på det seneste på MariaDB Java Connector. Så hvad er den aktuelle driverydelse?

Lad mig dele et benchmarkresultat af 3 jdbc-drivere, der tillader adgang til en MySQL/MariaDB-database: DrizzleJDBC, MySQL Connector/J og MariaDB java-connector.

Driverversioner er den seneste tilgængelige version i GA på tidspunktet for skrivning af denne blog:

  • MariaDB 1.5.3
  • MySQL 5.1.39
  • Drizzle 1.4

BENCHMARK

JMH er et Oracle-mikrobenchmarking-rammeværktøj udviklet af Oracle, leveret som openJDK-værktøjer, der vil være den officielle java 9-mikrobenchmarksuite. Dens karakteristiske fordel i forhold til andre rammer er, at den er udviklet af de samme fyre i Oracle, som implementerer JIT (Just In Time compilation) og tillader at undgå de fleste mikrobenchmark-fælder.

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

Tests er ret ligetil, hvis du er fortrolig med java.
Eksempel:

public class BenchmarkSelect1RowPrepareText udvider 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); } }offentlig abstrakt klasse BenchmarkSelect1RowPrepareAbstract udvider BenchmarkInit { private String request ="SELECT CAST(? som tegnsæt utf8)"; public String select1RowPrepare(Connection connection, MyState state) throws SQLException { try (PreparedStatement readyStatement =connection.prepareStatement(request)) { preparertStatement.setString(1, state.insertData[state.counter++]); try (Resultatsæt rs =preparertStatement.executeQuery()) { rs.next(); returner rs.getString(1); } } }}

Tests, der bruger INSERTs forespørgsler, sendes til en BLACKHOLE-motor med den binære log deaktiveret for at undgå IO og afhængighed af lagerydeevnen. Dette giver mere stabile resultater.
(Uden at bruge sorthulsmotoren og deaktivering af binær log vil udførelsestiderne variere op til 10%).

Benchmark er blevet udført på MariaDB Server 10.1.17 og MySQL Community Server 5.7.13 databaser. Følgende dokument viser resultater ved brug af de 3 drivere med MariaDB Server 10.1.17. For de komplette resultater inklusive dem med MySQL Server 5.7.13, se venligst linket nederst i dokumentet.

MILJØ

Eksekvering (klient og server) udføres på en enkelt serverdråbe på digitalocean.com ved hjælp af følgende parametre:

  • Java(TM) SE Runtime Environment (build 1.8.0_101-b13) 64bit (faktisk sidste version ved kørsel af dette benchmark)
  • Ubuntu 16.04 64bit
  • 512 Mb hukommelse
  • 1 CPU
  • database MariaDB “10.1.17-MariaDB”, MySQL Community Server build “5.7.15-0ubuntu0.16.04.1”
    ved hjælp af standardkonfigurationsfiler og disse yderligere muligheder:

    • max_allowed_packet =40M #udvekslingspakke kan være op til 40mb
    • character-set-server =utf8 #for at bruge UTF-8 som standard
    • sorteringsserver =utf8_unicode_ci #for at bruge UTF-8 som standard

Når det er angivet "fjern", køres benchmarks med separat klient og server på 2 identiske værter på samme datacenter med en gennemsnitlig ping på 0,350 ms.

RESULTATER EKSEMPEL FORKLARINGER

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

Dette betyder, at denne simple forespørgsel vil tage en gennemsnitlig tid på 62,715 mikrosekunder ved at bruge MariaDB-driveren med en variation på ± 2,402 mikrosekunder for 99,9 % af forespørgslerne.
Samme udførelse ved hjælp af støvregn-driver vil tage en gennemsnitlig tid på 88,670 mikrosekunder, og 78.672 mikrosekunder ved hjælp af MySQL-stik (mindre udførelsestid jo bedre).

De viste procenter er indstillet i henhold til mariadb første resultat som reference (100 %), hvilket gør det nemt at sammenligne andre resultater.

SAMMENLIGNINGER AF YDELSE

Benchmark vil teste ydeevnen af ​​de 3 vigtigste forskellige adfærd ved hjælp af en samme lokale database (samme server) og en fjern database (en anden identisk server) på samme datacenter med et gennemsnitligt ping på 0,450ms

Forskellig adfærd:

Tekstprotokol

Dette svarer til muligheden useServerPrepStmts deaktiveret.
Forespørgsler sendes direkte til serveren med udskiftning af rensede parametre udført på klientsiden.
Data sendes som tekst. Eksempel:Et tidsstempel vil blive sendt som teksten "1970-01-01 00:00:00.000500" med 26 bytes

Binær protokol

Dette svarer til muligheden useServerPrepStmts aktiveret (standardimplementering på MariaDB-driver).
Data sendes binært. Eksempeltidsstempel "1970-01-01 00:00:00.000500" vil blive sendt med 11 bytes.

Der er op til 3 udvekslinger med serveren for én forespørgsel :

  1. PREPARE – Forbereder erklæring til udførelse.
  2. UDFØR – Send parametre
  3. DEALLOCATE PREPARE – Frigiver en forberedt erklæring.

Se dokumentationen til serverforberedelse for at få flere oplysninger.

PREPARE-resultater gemmes i cache på førersiden (standardstørrelse 250). Hvis Prepare allerede er i cachen, vil PREPARE ikke blive udført, DEALLOCATE vil kun blive udført, når PREPARE ikke bruges længere og ikke i cachen. Det betyder, at nogle forespørgselsudførelser vil have 3 tur/retur, men nogle vil kun have én tur/retur, der sender en PREPARE identifikator og parametre.

Omskriv

Dette svarer til muligheden rewriteBatchedStatements aktiveret.
Rewrite bruger tekstprotokollen og vedrører kun batches. Driveren vil omskrive forespørgslen for hurtigere resultater.

Eksempel:
Indsæt i ab (i) værdier (?) med første batch-værdier [1] og [2] vil blive omskrevet til
Indsæt i ab (i) værdier (1), (2).

Hvis forespørgsel ikke kan omskrives i "multi-værdier", vil omskrivning bruge multi-forespørgsler :
Indsæt i tabel(col1) værdier (?) på dublet nøgleopdatering col2=? med værdierne [1,2] og [2,3] vil blive omskrevet til
Indsæt i tabel(col1) værdier (1) på dubletnøgleopdatering col2=2;Indsæt i tabel(col1) værdier (3) på dublet nøgleopdatering col2=4

Ulemper ved denne mulighed er:

  • Automatisk inkrement-id'er kan ikke hentes ved hjælp af Statement.html#getGeneratedKeys().
  • Flere forespørgsler i én udførelse er aktiveret. Det er ikke et problem for PreparedStatement, men hvis applikationen bruger Statement kan det være en sikkerhedsforringelse (SQL-injektion).

* MariaDB og MySQL har disse 3 adfærd implementeret, dryp kun tekstprotokollen.

BENCHMARKRESULTATER

MariaDB-driverresultater

ENKELT VALGT FORESPØRGSEL

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 tilfældige 100 bytes. try (Resultatsæt rs =preparertStatement.executeQuery()) { rs.next(); returner rs.getString(1); } }}
LOKAL DATABASE:BenchmarkSelect1RowPrepareHit.mariadb 58.267 ± 2.270 µs/opBenchmarkSelect1RowPrepareMiss.mariadb 118.896 ± 5.500 µs/opBenchmaritµs/opBenchmarkSelect1bµt.Prowpar1b.26 
FJERN DATABASE:BenchmarkSelect1RowPrepareHit.mariadb 394.354 ± 13.102 µs/opBenchmarkSelect1RowPrepareMiss.mariadb 709.843 ± 31.090 µs/opBenchmark 

Når PREPARE-resultatet for denne nøjagtige forespørgsel allerede er i cachen (cache-hit), vil forespørgslen være hurtigere (7,1 % i dette eksempel) end at bruge tekstprotokol. På grund af den ekstra anmodning PREPARE og DEALLOCATE-udvekslinger er cachemiss 68,1 % langsommere.

Dette understreger fordelene og ulemperne ved at bruge en binær protokol. Cache-HIT er vigtigt.

ENKELT INDSÆT FORESPØRGSEL

private String request ="INSERT INTO blackholeTable (charValue) values ​​(?)";public boolean executeOneInsertPrepare(Connection connection, String[] datas) kaster SQLException { try (PreparedStatement readyStatement =connection.prepareStatement(request)) { readyStatement. setString(1, data[0]); //en tilfældig 100 byte data returnerer readyStatement.execute(); }}
LOKAL DATABASE:BenchmarkOneInsertPrepareHit.mariadb 61.298 ± 1.940 µs/opBenchmarkOneInsertPrepareMiss.mariadb 130.896 ± 6.362 µs/opBenchmariOne 6.362 µs/opBenchmariOne6t.6t.8b 
FJERN DATABASE:BenchmarkOneInsertPrepareHit.mariadb 379.295 ± 17.351 µs/opBenchmarkOneInsertPrepareMiss.mariadb 802.287 ± 24.825 µs/opBenchmark 

Resultater for INSERTs ligner SELECTs resultater.

BATCH:1000 INDSÆT FORESPØRGSEL

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++) { readyStatement.setString(1, data[i]); //a tilfældig 100 byte data preparertStatement.addBatch(); } returner forberedtStatement.executeBatch(); }}
LOKAL DATABASE:PrepareStatementBatch100InsertPrepareHit.mariadb 5,290 ± 0,232 ms/opPrepareStatementBatch100InsertRewrite.mariadb 0,404 ± 0,014 ms/opPrepare100ement 1 
FJERN DATABASE:PrepareStatementBatch100InsertPrepareHit.mariadb 7.639 ± 0.476 ms/opPrepareStatementBatch100InsertRewrite.mariadb 1.164 ± 0.037 ms/opPrepare100ement 6.037 ms/opPrepare100x.5 

Brug af binær protokol er her mere betydningsfuldt, med resultater 13 % hurtigere end at bruge tekstprotokol.

Indsæt sendes i bulk og resultater læses asynkront (det svarer til optionuseBatchMultiSend). Dette giver mulighed for at opnå resultater på afstand med ydeevne ikke langt fra de lokale.

Rewrite har en fantastisk god ydeevne, men vil ikke have auto-increment id'er. Hvis du ikke har brug for id'er med det samme og ikke bruger ORM, vil denne løsning være den hurtigste. Nogle ORM tillader konfiguration til at håndtere sekvens internt for at give inkrement-id'er, men disse sekvenser er ikke distribueret, så de vil ikke fungere på klynger.

SAMMENLIGNING MED ANDRE DRIVERE

VÆLG forespørgsel med resultat på én række

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.µop.7RowPrepareText 

HA står for "High Availability" ved hjælp af Master-Slave-konfigurationen
(forbindelses-URL er "jdbc:mysql:replication://localhost:3306,localhost:3306/testj").

Disse resultater skyldes en masse forskellige implementeringsvalg. Her er nogle grunde, der forklarer tidsforskelle:

  • MariaDB-driveren er optimeret til UTF-8, hvilket tillader mindre oprettelse af bytes-array, undgår array-kopiering og hukommelsesforbrug.
  • HA-implementering:MariaDB- og MySQL-drivere bruger en dynamisk Java-proxyklasse, der sidder mellem Statement-objekter og sockets, hvilket tillader at tilføje failover-adfærd. Disse tilføjelser vil koste en overhead på 2 mikrosekunder pr. forespørgsel (62.715 uden at blive 64.676 mikrosekunder).
    I MySQL-implementering er næsten alle interne metoder proxy, hvilket tilføjer en overhead for masser af metoder, der ikke har noget at gøre med failover, tilføjer en samlet overhead på 50 mikrosekunder til hver forespørgsel.

(Drizzle har ingen PREPARE, hverken HA-funktionalitet)

"Vælg 1000 rækker"

private String request ="vælg * fra seq_1_to_1000"; //ved at bruge sekvenslagermaskinen privat ResultSet select1000Row(Connection connection) kaster SQLException { try (Statement statement =connection.createStatement()) { try (ResultSet rs =statement.executeQuery(request)) { while (rs.next()) { rs.getString(1); } returnere rs; } }
BenchmarkSelect1000Rows.mariadb 244,228 ± 7,686 µs/opBenchmarkSelect1000Rows.mysql 298,814 ± 12,143 µs/opBenchmarkSelect1000Rows. 

Når du bruger en masse data, bruges tiden for det meste på at læse fra socket og gemme resultatet i hukommelsen for at sende det tilbage til klienten. Hvis benchmark kun udførte SELECT uden at læse resultaterne, ville MySQL og MariaDB eksekveringstid svare til. Da målet med en SELECT-forespørgsel er at få resultater, er MariaDB-driveren optimeret til at give resultater tilbage (undgå oprettelse af bytes-arrays).

"Indsæt 1000 rækker"

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 og Drizzle bulk insert er som X INSERT:Driver sender 1 INSERT, vent på indsættelsesresultat, og send næste insert. Netværksforsinkelsen mellem hver indsættelse vil sænke indsættelser.

Butiksprocedurer

PROCEDURE OPKALD

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

MySQL- og MariaDB-implementeringer er fuldstændig forskellige. Mysql-driveren vil bruge mange skjulte forespørgsler for at opnå outputresultat:

  • SHOW CREATE PROCEDURE testj.inoutParam for at identificere IN- og OUT-parametre
  • SET @com_mysql_jdbc_outparam_p1 = 1 at sende data i henhold til IN / OUT-parametre
  • CALL testj.inoutParam(@com_mysql_jdbc_outparam_p1) opkaldsprocedure
  • SELECT @com_mysql_jdbc_outparam_p1 for at læse outputresultatet

MariaDB implementering er ligetil ved at bruge en mulighed for at have OUT parameter i serversvaret uden yderligere forespørgsler. (Det er hovedårsagen til, at MariaDB-driveren kræver MariaDB/MySQL-serverversion 5.5.3 eller nyere).

KONKLUSION

MariaDB driver rocker!

Den binære protokol har forskellige fordele, men er afhængig af at have PREPARE-resultaterne allerede i cachen. Hvis applikationer har mange forskellige slags forespørgsler, og databasen er fjern, er det måske ikke den bedre løsning.

Rewrite har fantastiske resultater til at skrive data i batch

Driver holder godt i forhold til andre drivere. Og der er meget i vente, men det er en anden historie.

Rå resultater:

  1. med en MariaDB 10.1.17-database lokal, fjern
  2. med en MySQL Community Server 5.7.15-database (build 5.7.15-0ubuntu0.16.04.1) lokal

  1. Sådan installeres pgAdmin 4 på Ubuntu 20.04/18.04/16.04

  2. Typer i MySQL:BigInt(20) vs Int(20)

  3. cx_Oracle forbinder ikke, når du bruger SID i stedet for tjenestenavn på forbindelsesstrengen

  4. Kode til at kalde en funktion i en pakke fra C# og ODP.NET