MariaDB
 sql >> Baza danych >  >> RDS >> MariaDB

Wydajność sterownika złącza Java MariaDB

WYDAJNOŚĆ ZŁĄCZA MARIADB JAVA

Zawsze mówimy o wydajności. Ale zawsze chodzi o „Zmierz, nie zgaduj!”.

Ostatnio w MariaDB Java Connector wprowadzono wiele ulepszeń wydajności. Więc jaka jest aktualna wydajność sterownika?

Pozwólcie, że podzielę się wynikiem testu porównawczego 3 sterowników jdbc umożliwiających dostęp do bazy danych MySQL/MariaDB:DrizzleJDBC, MySQL Connector/J i MariaDB java connector.

Wersje sterowników to najnowsza dostępna wersja GA w momencie pisania tego bloga:

  • MariaDB 1.5.3
  • MySQL 5.1.39
  • Mżawka 1.4

WSKAŹNIK

JMH to narzędzie Oracle micro-benchmarking opracowane przez Oracle, dostarczane jako narzędzia openJDK, które będzie oficjalnym pakietem microbenchmark java 9. Jego wyróżniającą przewagą nad innymi frameworkami jest to, że jest rozwijany przez tych samych facetów w Oracle, którzy wdrażają JIT (kompilacja Just In Time) i pozwalają uniknąć większości pułapek mikro-benchmarkowych.

Źródło testu porównawczego:https://github.com/rusher/mariadb-java-driver-benchmark.

Testy są całkiem proste, jeśli znasz java.
Przykład:

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);
            }
        }
    }
}

Testy wykorzystujące zapytania INSERT są wysyłane do mechanizmu BLACKHOLE z wyłączonym dziennikiem binarnym, aby uniknąć operacji we/wy i zależności od wydajności pamięci. Pozwala to na uzyskanie bardziej stabilnych wyników.
(Bez korzystania z silnika czarnej dziury i wyłączenia dziennika binarnego czasy wykonania mogą się różnić do 10%).

Testy porównawcze zostały wykonane na bazach danych MariaDB Server 10.1.17 i MySQL Community Server 5.7.13. Poniższy dokument przedstawia wyniki przy użyciu 3 sterowników z MariaDB Server 10.1.17. Aby uzyskać pełne wyniki, w tym te z MySQL Server 5.7.13, zobacz link na dole dokumentu.

ŚRODOWISKO

Wykonywanie (klient i serwer) odbywa się na pojedynczym serwerze droplet na digitalocean.com przy użyciu następujących parametrów:

  • Java(TM) SE Runtime Environment (kompilacja 1.8.0_101-b13) 64-bitowe (rzeczywista ostatnia wersja podczas uruchamiania tego testu porównawczego)
  • Ubuntu 16.04 64 bity
  • 512 MB pamięci
  • 1 procesor
  • baza danych MariaDB „10.1.17-MariaDB”, kompilacja MySQL Community Server „5.7.15-0ubuntu0.16.04.1”
    przy użyciu domyślnych plików konfiguracyjnych i tych dodatkowych opcji:

    • max_allowed_packet =40M #pakiet wymiany może mieć do 40mb
    • serwer-zestawu znaków =utf8 #aby używać UTF-8 jako domyślnego
    • sortowanie-serwer =utf8_unicode_ci #aby używać UTF-8 jako domyślnego

Gdy wskazano „odległe”, testy porównawcze są uruchamiane z oddzielnym klientem i serwerem na 2 identycznych hostach w tym samym centrum danych ze średnim pingiem 0,350 ms.

WYNIKI PRZYKŁADOWE WYJAŚNIENIA

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

Oznacza to, że to proste zapytanie zajmie średni czas 62,715 mikrosekund przy użyciu sterownika MariaDB z różnicą ± 2,402 mikrosekund dla 99,9% zapytań.
To samo wykonanie przy użyciu sterownika drizzle zajmie średni czas 88,670 mikrosekund, a 78,672 mikrosekund przy użyciu złącza MySQL (mniejszy czas wykonania, tym lepiej).

Wyświetlane wartości procentowe są ustawione zgodnie z pierwszym wynikiem mariadb jako odniesieniem (100%), co pozwala na łatwe porównanie innych wyników.

PORÓWNANIA WYDAJNOŚCI

Test porównawczy przetestuje wydajność 3 głównych różnych zachowań przy użyciu tej samej lokalnej bazy danych (ten sam serwer) i odległej bazy danych (inny identyczny serwer) w tym samym centrum danych ze średnim pingiem 0,450 ms

Różne zachowania:

Protokół tekstowy

Odpowiada to wyłączonej opcji useServerPrepStmts.
Kwerendy są wysyłane bezpośrednio do serwera, a zamiana parametrów oczyszczonych odbywa się po stronie klienta.
Dane są wysyłane jako tekst. Przykład:znacznik czasu zostanie wysłany w postaci tekstu „1970-01-01 00:00:00.000500” przy użyciu 26 bajtów

Protokół binarny

Odpowiada to włączonej opcji useServerPrepStmts (domyślna implementacja w sterowniku MariaDB).
Dane są wysyłane w postaci binarnej. Przykładowy znacznik czasu „1970-01-01 00:00:00.000500” zostanie wysłany z użyciem 11 bajtów.

Istnieje do 3 wymian z serwerem dla jednego zapytania :

  1. PREPARE – Przygotowuje oświadczenie do wykonania.
  2. WYKONAJ – Wyślij parametry
  3. ZDEJMIJ PRZYGOTOWANIE — publikuje przygotowane oświadczenie.

Zobacz Dokumentację przygotowania serwera, aby uzyskać więcej informacji.

Wyniki PREPARE są przechowywane w pamięci podręcznej po stronie sterownika (domyślny rozmiar 250). Jeśli Przygotuj jest już w pamięci podręcznej, PREPARE nie zostanie wykonane, DEALLOCATE zostanie wykonane tylko wtedy, gdy PREPARE nie jest już używane i nie znajduje się w pamięci podręcznej. Oznacza to, że wykonanie niektórych zapytań będzie miało 3 podróże w obie strony, ale niektóre będą miały tylko jedną podróż w obie strony, wysyłając identyfikator i parametry PREPARE.

Przepisz

Odpowiada to opcji rewriteBatchedStatements enabled.
Rewrite używa protokołu tekstowego i dotyczy tylko partii. Sterownik przepisze zapytanie, aby uzyskać szybsze wyniki.

Przykład:
Wstaw do ab (i) wartości (?) z pierwszymi wartościami partii [1] i [2] zostaną przepisane na
Wstaw do ab (i) wartości (1), (2).

Jeśli zapytanie nie może zostać przepisane w „wielowartościowych”, przepisanie użyje wielu zapytań :
Wstaw do tabeli(col1) wartości (?) przy aktualizacji zduplikowanego klucza col2=? z wartościami [1,2] i [2,3] zostaną przepisane na
Wstaw do table(col1) wartości (1) przy zduplikowanym kluczu aktualizacja col2=2;Wstaw do table(col1) wartości (3) na zduplikowana aktualizacja klucza col2=4

Wady tej opcji to:

  • Identyfikatorów automatycznego przyrostu nie można pobrać za pomocąStatement.html#getGeneratedKeys().
  • Włączono wiele zapytań w jednym wykonaniu. Nie stanowi to problemu dlaPreparedStatement, ale jeśli aplikacja używa instrukcji, może to spowodować pogorszenie bezpieczeństwa (wstrzyknięcie SQL).

* MariaDB i MySQL mają zaimplementowane te 3 zachowania, Drizzle tylko protokół Text.

WYNIKI BENCHMARK

Wyniki sterownika MariaDB

POJEDYNCZE ZAPYTANIE

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

Gdy wynik PREPARE dla tego dokładnego zapytania znajduje się już w pamięci podręcznej (trafienie w pamięci podręcznej), zapytanie będzie szybsze (7,1% w tym przykładzie) niż przy użyciu protokołu tekstowego. Ze względu na dodatkowe żądanie wymian PREPARE i DEALLOCATE, brak pamięci podręcznej jest o 68,1% wolniejszy.

Podkreśla to zalety i niedogodności korzystania z protokołu binarnego. HIT pamięci podręcznej jest ważny.

ZAPYTANIE Z POJEDYNCZYM WSTAWIENIEM

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

Wyniki dla INSERT są podobne do wyników SELECT.

PARTIA:1000 WSTAW ZAPYTANIE

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

Korzystanie z protokołu binarnego jest tutaj ważniejsze, dając wyniki o 13% szybsze niż przy użyciu protokołu tekstowego.

Inserty są wysyłane masowo, a wyniki odczytywane asynchronicznie (co odpowiada opcjiuseBatchMultiSend). Pozwala to na uzyskanie odległych wyników z wydajnością niedaleko od lokalnych.

Rewrite ma niesamowicie dobrą wydajność, ale nie będzie miał identyfikatorów automatycznego zwiększania. Jeśli nie potrzebujesz identyfikatorów od razu i nie korzystasz z ORM, to rozwiązanie będzie najszybsze. Niektóre ORM pozwalają konfiguracji na obsługę sekwencji wewnętrznie w celu zapewnienia identyfikatorów przyrostu, ale te sekwencje nie są rozproszone, więc nie będą działać w klastrach.

PORÓWNANIE Z INNYMI KIEROWCAMI

WYBIERZ zapytanie z wynikiem jednego wiersza

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 oznacza „High Availability” przy użyciu konfiguracji Master-Slave
(adres URL połączenia to „jdbc:mysql:replication://localhost:3306,localhost:3306/testj”).

Wyniki te wynikają z wielu różnych wyborów wdrożeniowych. Oto kilka powodów, które wyjaśniają różnice czasowe:

  • Sterownik MariaDB jest zoptymalizowany pod kątem UTF-8, pozwalając na mniej tworzenia tablicy bajtów, unikając kopiowania tablicy i zużycia pamięci.
  • Implementacja HA:sterowniki MariaDB i MySQL używają dynamicznej klasy Proxy Java znajdującej się między obiektami Statement i gniazdami, co pozwala na dodanie zachowania awaryjnego. Te dodanie będą kosztować 2 mikrosekundy na zapytanie (62.715 bez przekształcenia się w 64,676 mikrosekund).
    W implementacji MySQL prawie wszystkie metody wewnętrzne są udostępniane za pośrednictwem proxy, dodając obciążenie dla wielu metod, które nie mają nic wspólnego z przełączaniem awaryjnym, dodając łączny narzut 50 mikrosekund na każde zapytanie.

(Drizzle nie ma funkcji PREPARE, ani funkcji HA)

„Wybierz 1000 wierszy”

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

W przypadku korzystania z dużej ilości danych, czas spędzany jest głównie na odczytywaniu z gniazda i przechowywaniu wyników w pamięci w celu wysłania ich z powrotem do klienta. Gdyby benchmark wykonywał tylko SELECT bez odczytywania wyników, czas wykonania MySQL i MariaDB byłby równoważny. Ponieważ celem zapytania SELECT jest uzyskanie wyników, sterownik MariaDB jest zoptymalizowany pod kątem zwracania wyników (unikając tworzenia tablic bajtów).

„Wstaw 1000 wierszy”

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

Wstawianie zbiorcze MySQL i Drizzle jest jak X INSERT:Sterownik wysyła 1 INSERT, czeka na wynik wstawienia i wysyła następną wstawkę. Opóźnienie sieci między poszczególnymi wstawkami spowolni wstawianie.

Procedury sklepowe

PROCEDURA POŁĄCZENIA

//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

Implementacje MySQL i MariaDB całkowicie się różnią. Sterownik Mysql użyje wielu ukrytych zapytań, aby uzyskać wynik wyjściowy:

  • SHOW CREATE PROCEDURE testj.inoutParam do identyfikacji parametrów IN i OUT
  • SET @com_mysql_jdbc_outparam_p1 = 1 do wysyłania danych zgodnie z parametrami IN / OUT
  • CALL testj.inoutParam(@com_mysql_jdbc_outparam_p1) procedura połączenia
  • SELECT @com_mysql_jdbc_outparam_p1 odczytywanie wyniku wyjściowego

Implementacja MariaDB jest prosta dzięki możliwości posiadania parametru OUT w odpowiedzi serwera bez żadnych dodatkowych zapytań. (To główny powód, dla którego sterownik MariaDB wymaga serwera MariaDB/MySQL w wersji 5.5.3 lub nowszej).

WNIOSEK

Sterownik MariaDB buja!

Protokół binarny ma różne zalety, ale opiera się na tym, że wyniki PREPARE znajdują się już w pamięci podręcznej. Jeśli aplikacje mają wiele różnego rodzaju zapytań, a baza danych jest odległa, może to nie być lepszym rozwiązaniem.

Rewrite ma niesamowite wyniki, aby zapisywać dane w partii

Kierowca trzyma się dobrze w porównaniu z innymi kierowcami. A przed nami wiele, ale to już inna historia.

Surowe wyniki:

  1. z lokalną, odległą bazą danych MariaDB 10.1.17
  2. z bazą danych MySQL Community Server 5.7.15 (kompilacja 5.7.15-0ubuntu0.16.04.1) lokalna

  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Obsługa MariaDB 10.4 w zaktualizowanym dbForge Studio dla MySQL, v.8.1

  2. MariaDB JSON_SEARCH() Objaśnienie

  3. Jak działa FORMAT() w MariaDB?

  4. 8 sposobów na dodawanie dni do daty w MariaDB

  5. MariaDB JSON_UNQUOTE() Objaśnienie