Dachangインタビュアー:数千万のデータを含むテーブルをすばやくクエリする方法は?

推奨読書:

序文

  • インタビュアー:1000万のデータについてお話ししましょう。どのようにクエリを実行しますか?
  • 兄弟B:直接ページングクエリ、制限ページングを使用。
  • インタビュアー:練習した?
  • 兄弟B:あるに違いない

この瞬間「Cool and Cool」の曲を発表

何千万ものデータを持つテーブルに出会ったことがない人もいるかもしれませんし、何千万ものデータをクエリしたときに何が起こるかを知りません。

今日は実践的な演習をお見せしますが、今回はテスト用のMySQL 5.7.26基づいています。

データを準備する

1,000万のデータがない場合はどうなりますか?

作成する

1000万のコード作成?それは不可能です、それは遅すぎる、それは本当に一日かかるかもしれません。データベーススクリプトを使用すると、はるかに高速に実行できます。

テーブルを作成
CREATE TABLE `user_operation_log` (
  `id` int(11) NOT NULL AUTO_INCREMENT, 
  `user_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  `ip` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  `op_data` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  `attr1` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  `attr2` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  `attr3` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  `attr4` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  `attr5` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  `attr6` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  `attr7` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  `attr8` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  `attr9` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  `attr10` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  `attr11` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  `attr12` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, 
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; 
データスクリプトを作成する

バッチ挿入を使用すると、効率がはるかに速くなり、1000アイテムごとにコミットし、データが多すぎると、バッチ挿入の効率が低下します。

DELIMITER ;
;
CREATE PROCEDURE batch_insert_log()BEGIN  DECLARE i iNT DEFAULT 1;
  DECLARE userId iNT DEFAULT 10000000;
 set @execSql = 'INSERT INTO `test`.`user_operation_log`(`user_id`, `ip`, `op_data`, `attr1`, `attr2`, `attr3`, `attr4`, `attr5`, `attr6`, `attr7`, `attr8`, `attr9`, `attr10`, `attr11`, `attr12`) VALUES';
 set @execData = '';
  WHILE i<=10000000 DO   set @attr = "'测试很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长的属性'";
  set @execData = concat(@execData, "(", userId + i, ", '10.0.69.175', '用户登录操作'", ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ",", @attr, ")");
  if i % 1000 = 0  then     set @stmtSql = concat(@execSql, @execData,";");
    prepare stmt from @stmtSql;
    execute stmt;
    DEALLOCATE prepare stmt;
    commit;
    set @execData = "";
    else     set @execData = concat(@execData, ",");
   end if;
  SET i=i+1;
  END WHILE;
END;
;
DELIMITER ;

テストを開始

ブラザーのコンピューター構成は比較的低いです:win10標準圧力残留i5は約500MBのSSDを読み書きします

構成が少ないため、このテスト用に準備されたデータは3148000個のみで、ディスクの5Gを占有しました(インデックス付けなし)。38分間実行した後、コンピューター構成が適切な学生は、マルチポイントデータテストを挿入できます。

SELECT count(1) FROM `user_operation_log`

戻り結果:3148000

3つのクエリ時間は次のとおりです。

  • 14060ミリ秒
  • 13755 ms
  • 13447ミリ秒

通常のページングクエリ

MySQLは、指定された数のデータを選択するためにLIMITステートメントをサポートし、OracleはROWNUMを使用して選択できます。

MySQLページングクエリ構文は次のとおりです。

SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset
  • 最初のパラメーターは、最初に返されたレコード行のオフセットを指定します
  • 2番目のパラメーターは、返される行の最大数を指定します

次に、クエリ結果のテストを開始します。

SELECT * FROM `user_operation_log` LIMIT 10000, 10

3つのクエリの時間は次のとおりです。

  • 59ミリ秒
  • 49ミリ秒
  • 50ミリ秒

速度はまあまあのようですが、それはローカルデータベースであり、速度は当然高速です。

別の角度からテストする

同じオフセット、異なるデータ量
SELECT * FROM `user_operation_log` LIMIT 10000, 10
SELECT * FROM `user_operation_log` LIMIT 10000, 100
SELECT * FROM `user_operation_log` LIMIT 10000, 1000
SELECT * FROM `user_operation_log` LIMIT 10000, 10000
SELECT * FROM `user_operation_log` LIMIT 10000, 100000
SELECT * FROM `user_operation_log` LIMIT 10000, 1000000

クエリ時間は次のとおりです。

初めて 2回目 3回目
10個 53ms 52ms 47ミリ秒
100枚 50ms 60ミリ秒 55ms
1000枚 61ミリ秒 74ms 60ミリ秒
10,000 164ms 180ミリ秒 217ms
100000 1609ミリ秒 1741ms 1764ms
1000000 16219ms 16889ms 17081ミリ秒

上記の結果から結論を導き出すことができます:データの量が多いほど、時間がかかります

同じ量のデータ、異なるオフセット
SELECT * FROM `user_operation_log` LIMIT 100, 100
SELECT * FROM `user_operation_log` LIMIT 1000, 100
SELECT * FROM `user_operation_log` LIMIT 10000, 100
SELECT * FROM `user_operation_log` LIMIT 100000, 100
SELECT * FROM `user_operation_log` LIMIT 1000000, 100
オフセット 初めて 2回目 3回目
100 36ms 40ms 36ms
1000 31ms 38ms 32ms
10000 53ms 48ms 51ms
100000 622ms 576ms 627ミリ秒
1000000 4891ms 5076ms 4856ミリ秒

上記の結果から結論を導き出すことができます。オフセット大きいほど、時間がかかります

SELECT * FROM `user_operation_log` LIMIT 100, 100
SELECT id, attr FROM `user_operation_log` LIMIT 100, 100

最適化する方法

上記のトスを通過したので、上記の2つの問題について、大きなオフセットと大量のデータについて、最適化に進むという結論にも達しました。

大きなオフセットの問題を最適化する

サブクエリを使用

最初にオフセット位置のIDを特定し、次にデータをクエリできます

SELECT  *  FROM  `user_operation_log`  LIMIT 1000000, 
 10SELECT id FROM  `user_operation_log`  LIMIT 1000000, 
 1SELECT  *  FROM  `user_operation_log`  WHERE id  >=  (
  SELECT id FROM  `user_operation_log`  LIMIT 1000000, 
   1
)  LIMIT 10 

クエリ結果は次のとおりです。

SQL 時を過ごす
第1条 4818ms
第2条(索引なし) 4329ms
第2条(索引付き) 199ms
第3条(索引なし) 4319ms
第3条(索引付き) 201ms

上記の結果から結論を導き出します。

  • 最初のものは最も時間がかかります、3番目のものは最初のものよりわずかに優れています
  • サブクエリはインデックスをより速く使用します

短所:IDが増加している場合にのみ適用されます

増加しないIDの場合、次の表現を使用できますが、この欠点は、ページングクエリをサブクエリにしか配置できないことです。

注:一部のmysqlバージョンはin句の制限をサポートしていないため、複数のネストされた選択が使用されます

SELECT  *  FROM  `user_operation_log`  WHERE id IN (
  SELECT t.id FROM (
    SELECT id FROM  `user_operation_log`  LIMIT 1000000, 
     10
  )  AS t
) 
ID制限方式を採用

この方法はより要求が厳しく、IDは継続的に増加する必要があり、IDの範囲を計算してから、その間で使用する必要があります。SQLは次のとおりです。

SELECT * FROM `user_operation_log` WHERE id between 1000000 AND 1000100 LIMIT 100SELECT * FROM `user_operation_log` WHERE id >= 1000000 LIMIT 100

クエリ結果は次のとおりです。

SQL 時を過ごす
第1条 22ms
第2条 21ms

結果から、この方法は非常に高速であることがわかります

注:ここでのLIMITはアイテムの数を制限し、オフセットは使用しません

大量のデータの問題を最適化する

返されるデータの量も速度に直接影響します

SELECT * FROM `user_operation_log` LIMIT 1, 1000000

SELECT id FROM `user_operation_log` LIMIT 1, 1000000

SELECT id, user_id, ip, op_data, attr1, attr2, attr3, attr4, attr5, attr6, attr7, attr8, attr9, attr10, attr11, attr12 FROM `user_operation_log` LIMIT 1, 1000000

クエリ結果は次のとおりです。

SQL 時を過ごす
第1条 15676ミリ秒
第2条 7298ms
第3条 15960ms

結果から、不要な列を減らすことで、クエリの効率も大幅に向上することがわかります

1番目と3番目のクエリ速度はほぼ同じです。現時点では、間違いなく文句を言うでしょう、それで私は非常に多くのフィールドで何をしているのですか?Direct *は終わっていません

MySQLサーバーとクライアントは同じマシン上にあるため、クエリデータは類似していることに注意してください。条件付きの学生は、クライアントとMySQLを個別にテストできます

SELECT *香りませんか?

ところで、ここにSELECT *を禁止すべき理由を追加してください。それは単純で頭のないものではありませんか?

2つの主なポイント:

  1. 「SELECT *」を使用すると、データベースはより多くのオブジェクト、フィールド、権限、属性、​​およびその他の関連コンテンツを解析する必要があります。複雑なSQLステートメントとより難しい解析の場合、データベースに大きな負荷がかかります。
  2. ネットワークのオーバーヘッドを増やします。*ログやIconMD5などの役に立たない大きなテキストフィールドが誤って表示され、データ転送サイズが幾何学的に大きくなります。特にMySQLとアプリケーションが同じマシン上にない場合、このオーバーヘッドは非常に明白です。

終わり

最後に、皆さんが一人でできることを願っています。

私はあなたのためにスクリプトを作成しました、あなたは何を待っていますか!

おすすめ

転載: blog.csdn.net/weixin_45784983/article/details/108295166