ビッグデータ規模を最適化する方法や詳細な方法、個人的な計測結果など、ある程度参考になります

1. 数字を作るための準備

1. スレッドプールの作成

データベーススクリプト:

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
  `username` varchar(100) DEFAULT NULL COMMENT '姓名',
  `sex` varchar(2) DEFAULT NULL COMMENT '性别',
  `age` int(3) DEFAULT NULL COMMENT '年龄',
  `phone` varchar(12) DEFAULT NULL COMMENT '手机号',
  `address` varchar(100) DEFAULT NULL COMMENT '家庭住址',
  `deptid` int(11) DEFAULT NULL COMMENT '归属部门ID',
  `udesc` varchar(255) DEFAULT NULL COMMENT '个人描述',
  `createtime` datetime DEFAULT NULL COMMENT '创建时间',
  `school` varchar(255) DEFAULT NULL COMMENT '毕业院校',
  `major` varchar(255) DEFAULT NULL COMMENT '专业名称',
  `nationality` varchar(255) DEFAULT NULL COMMENT '国籍',
  `nation` varchar(255) DEFAULT NULL COMMENT '民族',
  `idcard` varchar(255) DEFAULT NULL COMMENT '身份证号码',
  `qq` varchar(255) DEFAULT NULL COMMENT 'QQ号',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户信息表';

スレッド プールを介して 200 万のデータをデータベースに追加します。実行が完了すると、データベースには次のデータが保存されます。

ここに画像の説明を挿入します

2. 関数の作成

まず次の関数を作成します。

CREATE FUNCTION `insert_user`(`num` int) RETURNS int(11)
BEGIN
DECLARE i int DEFAULT 0;
WHILE i<num DO

if i % 2 = 0 then
	INSERT into t_user (username, sex, age, phone, address, deptid, udesc, createtime, school, major, nationality, nation, idcard, qq) 
	VALUES (concat('张三', i, '号'), '男', FLOOR(RAND()*100), CONCAT('18',FLOOR(rand()*(999999999-100000000)+100000000)), '湖北省武汉市江汉路191号', 25, '我是一个好人', NOW(), '武汉大学', '计算机应用技术', '中国', '汉族', '429004155130213602', '1134135987');
ELSE
	INSERT into t_user (username, sex, age, phone, address, deptid, udesc, createtime, school, major, nationality, nation, idcard, qq) 
	VALUES (concat('李四', i, '号'), '女', FLOOR(RAND()*100), CONCAT('18',FLOOR(rand()*(999999999-100000000)+100000000)), '湖北省武汉市江汉路192号', 26, '我是一个老好人', NOW(), '北京大学', '计算机网络技术', '中国', '回族', '429004155130213603', '1134135988');
end if;

set i = i+1;
END WHILE;
RETURN i;
END

次に関数を実行し、パラメーターとして 200 万を入力すると、実行結果は次のようになります。

ここに画像の説明を挿入します

速度はまあまあ、100秒程度

2.SQLテスト

1. サンプル SQL

さて、データベース テーブルにはすでに 200 万個のデータがあります。最初のデータから開始して 50 個のデータをオフセットして、次の SQL を実行してみましょう。

select * from t_user where sex = '男' limit 0, 50;

検索結果:

ここに画像の説明を挿入します

0.035秒かかりますが、それでも十分な速度です

2. 問題が発生する

さて、それでは、多くのページ後のデータの検索をシミュレートします。120W のデータが 50 個のデータによってオフセットされた後、次の SQL を実行します。

select * from t_user where sex = '男' limit 900000, 50;

検索結果:

ここに画像の説明を挿入します

このクエリには確かに 8.487 秒かかりましたが、これはすでに非常に遅いのですが、これはなぜでしょうか?

3. 原因分析

現時点での理由を分析するには、まず説明の実行計画を確認し、次のように SQL の前に [explain] キーワードを追加します。

explain select * from t_user where sex = '男' limit 900000, 50;

ここに画像の説明を挿入します

実行結果から、SQL クエリを実行するときに完全テーブル スキャンが使用されていることがわかりますが、これは非常に遅いはずです。

どうやって解決すればいいでしょうか?

4. インデックスの追加にはまだ問題があります

最初に行うことは、インデックスを追加してインデックスを作成することです。そうすることで初めてクエリの速度が向上します。それでは、性別フィールドにインデックスを追加します。

ここに画像の説明を挿入します

注: 通常、インデックスはテーブルの作成時に追加されますが、データベースにはすでに 200 万のデータがあるため、今インデックスを追加すると非常に時間がかかります。

さて、同じ SQL をもう一度実行して、時間がどのようになるかを見てみましょう。

select * from t_user where sex = '男' limit 900000, 50;

結果:

ここに画像の説明を挿入します

結果を見て混乱しました。インデックスが構築された後、クエリ時間が実際に増加しました。これはなぜですか?インデックスによってクエリ効率が向上するということではないでしょうか?

まず実行計画を見てみましょう。

explain select * from t_user where sex = '男' limit 900000, 50;

ここに画像の説明を挿入します

5. 原因分析

上記の実行プランからわかるように、インデックス [idx_user_sex] はヒットしましたが、それでも比較的遅いです。

その理由は次のとおりです。

  1. idx_user_sex は非クラスター化インデックス (非クラスター化インデックス) に属しており、クエリするステートメントは select * であり、これには他のフィールドが含まれます。非クラスター化インデックス idx_user_sex を通じて見つかったデータ列には、性別と ID のみが含まれます。他のフィールドを取得するには、ID に基づいて他のフィールド値をクエリする必要があります。これがテーブルの戻り操作です。

  2. まずクエリデータを制限するためのルールを明確にする必要があります

    例:limit m, n の場合、プロセスは次のとおりです。まず SQL 条件を満たすデータを特定し、次に m + n に基づいてスキャンする必要がある合計データを計算します。< a i=1> 合計量はいくらですか? 次に、データ行を先頭から m まで走査し、戻りセットに投入し始めます。失われる行の数は何行ですか? n 個のアイテムを失う

次に、制限のルールに従って、制限 0,50 を見てみましょう。

0+50=50を計算して、条件に合うものを50個取り出し、先頭の数字0と一致すればOK、戻りセットに0からデータを放り込めばOKです。

どれくらい失われましたか? 2 番目の数値は 50 なので、1 つのアイテムが失われ、50 のアイテムが失われ、最後にデータが返されます。

制限 900000,50 を見てみましょう。

900,000+50=900050

50 個のデータを取得するには、900050 個のデータをスキャンする必要があり、すぐに最初の数値を取得することを意味します。これは 900,000 です。最初の 900,000 個の意味のないデータを破棄し、次に 2 番目の数値が 50 であると判断し、50 個のデータ全体を戻りセットに投入し始め、最後にデータを返します。

最初の 900,000 クエリは無意味であることがわかりました

6. 最適化を続ける

まずリターンテーブルの最適化ですが、ノンクラスタードインデックスの性別がヒットした際に、条件として戻りデータがわかっているセットのIDを取得できればIDを直接取得できるので、データカラムを取得することができます。 IDなので便利です。

上記の SQL を次のように変更します。

select t1.* from (select id from t_user where sex = '男' limit 900000, 50) t2, t_user t1 where t2.id = t1.id

直接的な結果:

ここに画像の説明を挿入します

同じデータを見つけるのにかかる時間はわずか 0.293 秒で、クエリ速度は実際に何倍にも向上しました。

実行計画をもう一度見てみましょう。

EXPLAIN select t1.* from (select id from t_user where sex = '男' limit 900000, 50) t2, t_user t1 where t2.id = t1.id

結果:

ここに画像の説明を挿入します

このとき、Extra インジケーターには [Using where] と [Using Index] の両方が表示されますが、どちらもカバーインデックスを使用することと、インデックスを使用してインデックスキー値の検索を行うため、非常に効率的であることを示します。

rows の値は大きくなりますが、カバーリングインデックスが使用されるため、クエリ効率は非常に高くなります。

このように書くことを好む人もいます。

select t.* from t_user t where t.id in(
select id from (select id from t_user where sex = '男' limit 900000, 50) u
)

ここに画像の説明を挿入します

実際にはもっと時間がかかりましたが、実行計画を見るとわかります。

ここに画像の説明を挿入します

1,797,931 行のデータをはっきりと確認できる場合は、テーブル全体をスキャンする必要がありますが、これには時間がかかるはずです。

7. 徹底的な最適化

ID が連続している場合は、ID に基づいて 1 回フィルタリングすることができ、ページをめくるたびに ID 値がフロントエンドからバックエンドに渡されます。

たとえば、前のページの最後のレコードの ID 値が 1800006 である場合、ページをめくるときに次の SQL を実行できます。

select * from t_user where id >= (select id from t_user where sex = '男' limit 900000, 1) limit 50;

結果:

ここに画像の説明を挿入します

実際に実行時間が短いことがわかります。データ量が増加するにつれて、時間の違いはより明確になります。実行計画を見てみましょう。

ここに画像の説明を挿入します

主キーインデックスもヒットするため、より効率的です。

要約する

この大量のデータ ページング クエリの後、実際の開発でクエリ効率を向上させるためにインデックスを効果的に使用する方法がわかりました。

1. 遅延された関連付け

まず、where 条件を使用して主キーを抽出し、次にテーブルを元のデータ テーブルに関連付け、元のセカンダリ インデックスを使用してデータ行を抽出する代わりに、主キー ID を使用してデータ行を抽出します。次に例を示します。

select t1.* from (select id from t_user where sex = '男' limit 900000, 50) t2, t_user t1 where t2.id = t1.id

2.ブックマーク方法

率直に言うと、ブックマーク方法は、limit の最初のパラメーターに対応する主キー値を見つけて、この主キー値に基づいてフィルター処理および制限を行うことです。次に例を示します。

select * from t_user where id >= (select id from t_user where sex = '男' limit 900000, 1) limit 50;

おすすめ

転載: blog.csdn.net/Libigtong/article/details/134289729