MYSQL開発パフォーマンス調査:バッチデータ挿入の最適化方法

MYSQL開発パフォーマンス調査:バッチデータ挿入の最適化方法

1. どのような問題に直面しましたか?

標準 SQL では、通常、次の SQL 挿入ステートメントを記述します。

TBL_TEST(id) VALUES(1)に挿入します。

明らかに、このアプローチは MYSQL でも実行可能です。ただし、データをバッチで挿入する必要がある場合、このようなステートメントはパフォーマンスの問題を引き起こします。たとえば、100,000 個のデータを挿入する必要がある場合は、100,000 個の挿入ステートメントが必要になります。これらの各ステートメントは、実際の挿入作業を行うためにストレージ エンジンに到達する前に、解析と最適化のためにリレーショナル エンジンに送信する必要があります。

MYSQL の公式ドキュメントでバッチ挿入、つまり 1 つの INSERT ステートメントで複数の値を挿入することの使用について言及されているのは、まさにパフォーマンスのボトルネックの問題のためです。今すぐ、

TBL_TEST (id) に値 (1)、(2)、(3) を挿入する

このアプローチは、確かにバッチ挿入の高速化に役立ちます。その理由は理解しにくいものではありません。サーバーに送信される INSERT ステートメントが少なくなるため、ネットワーク負荷が軽減されます。最も重要なことは、解析と最適化の時間が長くなるように見えますが、実際には影響を受けるデータ行の数ははるかに多いということです。したがって、全体的なパフォーマンスが向上します。インターネット上のいくつかの主張によれば、この方法により効率が数十倍向上する可能性があるとのことです。

ただし、インターネット上では、SQL の前処理やバッチ送信など、他の方法もいくつか見かけます。では、これらの方法はどのように機能するのでしょうか?この記事ではこれらの方法を比較します。

2. 環境と方法の比較
私の環境はかなり貧弱で、基本的には単なる下位の仮想マシンです。コアは2つ、メモリは6Gしかありません。オペレーティング システムは SUSI Linux、MYSQL バージョンは 5.6.15 です。

ご想像のとおり、このマシンのパフォーマンスにより TPS が非常に低くなったため、以下のデータはすべて無意味ですが、傾向は異なり、挿入全体のパフォーマンスの傾向を示すことができます。

弊社の業務の特性上、使用するテーブルは非常に大きく、合計 195 個のフィールドがあり、完全に書き込まれると (varchar を含む各フィールドが完全に入力されると)、サイズは 4KB 弱になります。一般的に、レコードのサイズは 3KB です。

実際の経験から、単一のトランザクションで多数の INSERT ステートメントを送信することで、パフォーマンスが大幅に向上することを確信しています。したがって、以下のすべてのテストは、5,000 件のレコードが挿入されるたびにコミットするという実践に基づいています。

最後に、以下のすべてのテストは MYSQL C API を使用して実行され、INNODB ストレージ エンジンが使用されたことに注意してください。

3. 比較方法

理想型テスト(I) - 方法の比較

目的: 理想的な状況下での最も適切な挿入メカニズムを見つける

主な方法:

1. 各プロセス/スレッドは主キーの順に挿入する

2. さまざまな挿入方法の比較

3. プロセス数/スレッド数の違いが挿入に与える影響を比較する

image

※「通常の方法」とは、INSERT文でVALUEを1つだけ挿入する場合を指します。

* 「準備された SQL」とは、前処理された MYSQL C API が使用される場合を指します。

※「複数テーブル値SQL(10件)」とは、1つのINSERT文で10件のレコードを挿入することを意味します。なぜ10なのですか?その後の検証により、この方法が最高のパフォーマンスを発揮することがわかりました。

結論として、3つの方法の傾向から、マルチテーブル値SQL(10項目)方式が最も効率的であることがわかります。

理想テスト(II) - 複数のテーブル値を持つSQL文の数の比較

image

明らかに、データ量が増えるにつれて、INSERT ステートメントごとに 10 件のレコードを挿入するのが最も効率的なアプローチになります。

理想テスト(III) - 接続数の比較

image

image

結論: 接続数と操作数がCPUコア数の2倍のときにパフォーマンスが最高になります

一般的なテスト - 業務量に基づいたテスト

目的: 一般的なトランザクション状況に最適な挿入メカニズムは何ですか?

主な方法:

1. 生産データをシミュレートする(レコードあたり約3KB)

2. 各スレッドは主キーを順番通りに挿入しない

image

当然ですが、データが主キーに従って順序どおりに挿入されない場合、パフォーマンスは大幅に低下します。これは実際に、INNODB の内部実装原理によって示される現象と一致しています。しかし、マルチテーブル値 SQL (10 項目) の場合が最適であることは依然として確かです。

ストレステスト

目的: 極端な取引状況に最適な挿入メカニズムは?

主な方法:

1. データ行の各フィールドを入力します(各レコードは約4KBです)

2. 各スレッドは主キーを順番通りに挿入しない

image

結果は以前のパターンと似ていますが、パフォーマンスが極端に低下しています。そして、ここでは、レコード サイズが大きくなると (ページ サイズを超える場合もありますが、結局、スロットとページ ヘッドの情報がまだスペースを占有しています)、ページ分割などの現象が発生し、パフォーマンスが低下することが確認されています。

IV. 結論

上記のテストと INNODB に関する理解に基づいて、次の結論を導き出すことができます。

• 順次的な主キー戦略を使用する(主キーの自動増分、挿入されたレコードが可能な限り順次的になるようにビジネスロジックを変更するなど)

• 挿入には、マルチ値テーブル(10エントリ)を使用するのが最適です。

• プロセス/スレッド数をCPU数の2倍に制御するのが比較的適切である。

V. 付録

インターネット上には、MYSQL の SQL ステートメントを前処理する完全な例がほとんどないことがわかりました。ここに簡単な例を示します。

--テーブルステートメントの作成 CREATE TABLE tbl_test 
(
  pri_key varchar(30)、 
  nor_char char(30)、 
  最大数 10進数(8,0) 
  long_num DECIMAL(12, 0)、 
  rec_upd_ts タイムスタンプ
);

Cコード

#include <文字列.h>
#include <iostream>
#include <mysql.h>
#include <sys/time.h>
#include <sstream>
#include <ベクター>
 
名前空間 std を使用します。
 
#STRING_LEN 30 を定義します
  
char pri_key[STRING_LEN] = "123456"; 
char nor_char [STRING_LEN] = "abcabc"; 
char rec_upd_ts [STRING_LEN] = "NOW()"; 
 
bool SubTimeval(timeval &result, timeval &begin, timeval &end)
{
  begin.tv_sec>end.tv_sec の場合、false を返します。
 
  もし ( (begin.tv_sec == end.tv_sec) && (begin.tv_usec > end.tv_usec) )  
    false を返します。
 
  result.tv_sec = ( end.tv_sec - begin.tv_sec );  
  result.tv_usec = ( end.tv_usec - begin.tv_usec );  
 
  (結果.tv_usec<0)の場合{
    結果.tv_sec--;
    結果.tv_usec+=1000000;} 
  true を返します。
}
 
int main(int argc, char ** argv)
{
  INT32 戻り値 = 0;
  char errmsg[200] = {0};
  int sqlコード = 0;
 
  timeval tBegin、tEnd、tDiff;
   
  const char* precompile_statment2 = "`tbl_test`( pri_key、nor_char、max_num、long_num、rec_upd_ts) VALUES(?、?、?、?、?) に INSERT INTO";
   
  MYSQL 接続;
  mysql_init(&conn);
   
  (mysql_real_connect(&conn, "127.0.0.1", "dba", "abcdefg", "TESTDB", 3306, NULL, 0) == NULL)の場合
  {
    fprintf(stderr, "mysql_real_connect, 2 が失敗しました\n");
    終了(0);
  }
   
  MYSQL_STMT *stmt = mysql_stmt_init(&conn);
  if (!ステートメント)
  {
   fprintf(stderr, "mysql_stmt_init, 2 が失敗しました\n");
   fprintf(stderr, " %s\n", mysql_stmt_error(stmt));
   終了(0);
  }
   
  if (mysql_stmt_prepare(stmt、precompile_statment2、strlen(precompile_statment2)))
  {
   fprintf(stderr, "mysql_stmt_prepare, 2 が失敗しました\n");
   fprintf(stderr, " %s\n", mysql_stmt_error(stmt));
   終了(0);
  }
   
  整数 i = 0; 
  整数最大数 = 3;
  定数 int FIELD_NUM = 5;
  (i < 最大数) の間
  {
    //MYSQL_BIND バインド[196] = {0};
    MYSQL_BIND バインド[フィールド番号];
    memset(bind, 0, FIELD_NUM * sizeof(MYSQL_BIND));
   
    符号なしlong str_length = strlen(pri_key);
    バインド[0].buffer_type = MYSQL_TYPE_STRING;
    bind[0].buffer = (char *)pri_key;
    bind[0].buffer_length = STRING_LEN;
    バインド[0].is_null = 0;
    バインド[0].長さ = &str_length;
     
    符号なしlong str_length_nor = strlen(nor_char);
    バインド[1].buffer_type = MYSQL_TYPE_STRING;
    バインド[1].バッファ = (char *)nor_char;
    バインド[1].buffer_length = STRING_LEN;
    バインド[1].is_null = 0;
    バインド[1].length = &str_length_nor;
     
    バインド[2].buffer_type = MYSQL_TYPE_LONG;
    bind[2].buffer = (char*)&max_num;
    バインド[2].is_null = 0;
    バインド[2].長さ = 0;
     
    バインド[3].buffer_type = MYSQL_TYPE_LONG;
    bind[3].buffer = (char*)&max_num;
    バインド[3].is_null = 0;
    バインド[3].長さ = 0;
     
    MYSQL_TIME ts;
    ts.year = 2002;
    ts.月 = 02;
    ts.day = 03;
    ts.時間 = 10;
    ts.分 = 45;
    ts.秒 = 20;
     
    符号なしロングstr_length_time = strlen(rec_upd_ts);
    バインド[4].buffer_type = MYSQL_TYPE_TIMESTAMP;
    bind[4].buffer = (char *)&ts;
    バインド[4].is_null = 0;
    バインド[4].長さ = 0;
     
    if (mysql_stmt_bind_param(stmt, bind))
    {
      fprintf(stderr, "mysql_stmt_bind_param, 2 が失敗しました\n");
      fprintf(stderr, " %s\n", mysql_stmt_error(stmt));
      終了(0);
    }
     
    cout << "実行前\n";
    (mysql_stmt_execute(stmt)) の場合
    {
     fprintf(stderr, "mysql_stmt_execute, 2 が失敗しました\n");
     fprintf(stderr, " %s\n", mysql_stmt_error(stmt));
     終了(0);
    }
    cout << "実行後\n";
     
    私は++;
  }
   
  mysql_commit(&conn);
   
  mysql_stmt_close(stmt);
 
  0を返します。  
}

以上がMySQLバッチデータ挿入の最適化方法です。123WORDPRESS.COMの過去の記事も併せて読むことをお勧めします。

以下もご興味があるかもしれません:
  • MySQL に大量のデータを挿入する 4 つの方法の例
  • MYSQL バッチ挿入データ実装コード
  • MySQL でバッチ挿入を実装してパフォーマンスを最適化するチュートリアル
  • ユニークインデックスを使用したMySQLバッチ挿入を回避する方法
  • MySQLは挿入を使用して複数のレコードを挿入し、データを一括で追加します。
  • MySQL バッチデータ挿入スクリプト
  • MySQL バッチ挿入ループの詳細なサンプルコード
  • MySQL バッチ SQL 挿入パフォーマンス最適化の詳細な説明
  • MySql バッチ挿入の最適化 SQL 実行効率の例の詳細な説明
  • MySQL に 1,000 万件のレコードを素早く挿入する方法

<<:  GNU Parallelの具体的な使用法

>>:  wavesurfer.js によるオーディオ波形描画の実装

推薦する

デザイン理論:計画、リソース、コミュニケーションの問題について

<br />多くの中小企業ではこの問題は発生しません。中小企業はデザイナーをサポートし、...

JavaScript の実行コンテキストとコールスタックの詳細な説明

目次1. 実行コンテキストとは何か2. 実行コンテキスト スタックとは何ですか? 3. 実行コンテキ...

HTTPS の原則の説明

HTTPS ウェブサイトの構築コストが下がるにつれて、ほとんどのウェブサイトが HTTPS プロトコ...

Vue はシェイク機能を実装します (ios13.3 以降と互換性があります)

最近、shake.jsを使用して、shakeに似た機能を作成しました。ただし、shake機能はios...

Windows 10 システムに mysql-8.0.13 (zip インストール) をインストールする詳細なチュートリアル

インストール環境の説明•システムバージョン: windows10 •MySQL バージョン: mys...

mysql5.6.8 ソースコードのインストールプロセス

カーネル: [root@opop ~]# cat /etc/centos-release CentO...

Linux での wget コマンドの基本的な使い方

目次序文1. wgetを使用して単一のファイルをダウンロードする2. wget -Oを使用してダウン...

ベスト HTML/CSS デザインおよび開発フレームワーク 15 選を紹介します

プロフェッショナルな Web デザインは複雑で時間がかかります。 HTML と CSS フレームワー...

Ubuntu 16.04 サーバーで MySQL を設定し、リモート接続を有効にする方法

背景最近、Node.js を勉強しているのですが、クラウド サーバーがあることを思い出しました。しか...

Ubuntu 20.04 に MySql5.7 をインストールして構成するための詳細なチュートリアル

目次1. Ubuntuソースの変更2. MySQLをインストールする3. 新しいユーザーを作成し、権...

小さなアイコンのフロントエンド処理ソリューションのグラフィカルな説明

序文この記事を始める前に、複数選択の質問をしてみましょう。フロントエンド開発でビルド ツールを使用す...

テーブルタグ(TAGS)の詳細な紹介

テーブルの基本構文<table>...</table> - テーブルを定義し...

Nginx 運用保守ドメイン名検証方法例

インターフェイス ドメイン名を構成する際、各パブリック プラットフォームはドメイン名に対する開発者の...

Linux環境で環境変数を設定する方法

JDKダウンロードアドレス: http://www.oracle.com/technetwork/j...

MySQLカスタム関数とストアドプロシージャの詳細な説明

序文この記事では主にMySQLのカスタム関数とストアドプロシージャに関する関連コンテンツを紹介し、皆...