推奨読書:
-
インタビューの質問72件、合計3170ページをまとめ、30以上のインターネット企業のオファー(BATJMを含む)を獲得しました
-
私は2020年に最初の戦いで勝利しました。このJavaインタビューマジックプラスバージョンを使用すると、Ali、JD.com、Bytedanceなどの主要メーカーからオファーを得ることができました。
序文
- インタビュアー: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つの主なポイント:
- 「SELECT *」を使用すると、データベースはより多くのオブジェクト、フィールド、権限、属性、およびその他の関連コンテンツを解析する必要があります。複雑なSQLステートメントとより難しい解析の場合、データベースに大きな負荷がかかります。
- ネットワークのオーバーヘッドを増やします。*ログやIconMD5などの役に立たない大きなテキストフィールドが誤って表示され、データ転送サイズが幾何学的に大きくなります。特にMySQLとアプリケーションが同じマシン上にない場合、このオーバーヘッドは非常に明白です。
終わり
最後に、皆さんが一人でできることを願っています。!
私はあなたのためにスクリプトを作成しました、あなたは何を待っていますか!!!