Maison > base de données > tutoriel mysql > le corps du texte

Intervieweur : Comment interroger rapidement des dizaines de millions de données ?

Libérer: 2023-08-16 17:02:37
avant
1012 Les gens l'ont consulté

Regardons d'abord une scène d'interview :

  • Intervieweur : Parlons de 10 millions de données, comment les avez-vous interrogées ?
  • Brother : requête directement par pagination, utilisez la pagination limitée.
  • Intervieweur : L'avez-vous déjà fait en pratique ?
  • Petit frère : Il doit y en avoir un

Peut-être que certains amis n'ont jamais rencontré de table avec des dizaines de millions de données, et ils ne savent pas ce qui se passera lors de l'interrogation de dizaines de millions de données.

Aujourd'hui, je vais vous présenter une opération pratique. Cette fois, elle est basée sur la version MySQL 5.7.26 pour tester

Préparer les données

Que faire si vous n'avez pas 10 millions de données. ?

Créer

Code pour créer 10 millions ? C'est impossible, c'est trop lent et cela peut prendre une journée entière. Vous pouvez utiliser des scripts de base de données pour une exécution beaucoup plus rapide.

Créer une table
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;
Copier après la connexion
Créer un script de données

En utilisant l'insertion par lots, l'efficacité sera beaucoup plus rapide et tous les 1 000 éléments seront validés. La quantité de données est trop importante, ce qui entraînera également un ralentissement de l'efficacité de l'insertion par lots

.
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 = "&#39;测试很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长很长的属性&#39;";
  set @execData = concat(@execData, "(", userId + i, ", &#39;10.0.69.175&#39;, &#39;用户登录操作&#39;", ",", @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 ;
Copier après la connexion

开始测试

田哥的电脑配置比较低:win10 标压渣渣i5 读写约500MB的SSD

由于配置低,本次测试只准备了3148000条数据,占用了磁盘5G(还没建索引的情况下),跑了38min,电脑配置好的同学,可以插入多点数据测试

SELECT count(1) FROM `user_operation_log`
Copier après la connexion

返回结果:3148000

三次查询时间分别为:

  • 14060 ms
  • 13755 ms
  • 13447 ms

普通分页查询

MySQL 支持 LIMIT 语句来选取指定的条数数据, Oracle 可以使用 ROWNUM 来选取。

MySQL分页查询语法如下:

SELECT * FROM table LIMIT [offset,] rows | rows OFFSET offset
Copier après la connexion
  • 第一个参数指定第一个返回记录行的偏移量
  • 第二个参数指定返回记录行的最大数目

下面我们开始测试查询结果:

SELECT * FROM `user_operation_log` LIMIT 10000, 10
Copier après la connexion

查询3次时间分别为:

  • 59 ms
  • 49 ms
  • 50 ms

这样看起来速度还行,不过是本地数据库,速度自然快点。

换个角度来测试

相同偏移量,不同数据量
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
Copier après la connexion

查询时间如下:

10 000 articles 1000000 articles16219ms16889ms17081ms

Des résultats ci-dessus, nous pouvons conclure : Plus la quantité de données est importante, plus cela prend de temps

相同数据量,不同偏移量
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
Copier après la connexion
QuantitéPremière foisDeuxième foisTroisième fois
10 articles53ms52ms47ms
100 articles50ms60ms 55ms
1000 articles61ms74ms60ms
偏移量第一次第二次第三次
10036ms40ms36ms
100031ms38ms32ms
1000053ms48ms51ms
100000622ms576ms627ms
10000004891ms5076ms4856ms

从上面结果可以得出结束:偏移量越大,花费时间越长

SELECT * FROM `user_operation_log` LIMIT 100, 100
SELECT id, attr FROM `user_operation_log` LIMIT 100, 100
Copier après la connexion

如何优化

既然我们经过上面一番的折腾,也得出了结论,针对上面两个问题:偏移大、数据量大,我们分别着手优化

优化偏移量大问题

采用子查询方式

我们可以先定位偏移位置的 id,然后再查询数据

SELECT * FROM `user_operation_log` LIMIT 1000000, 10

SELECT id FROM `user_operation_log` LIMIT 1000000, 1

SELECT * FROM `user_operation_log` WHERE id >= (SELECT id FROM `user_operation_log` LIMIT 1000000, 1) LIMIT 10
Copier après la connexion

查询结果如下:

sqlÇa prend du temps
Le premier4818ms
Le deuxième (sans index)4329ms
Article 2 (avec index) 199ms
Le troisième article (sans index)4319ms
Le troisième article (avec index)201ms

从上面结果得出结论:

  • 第一条花费的时间最大,第三条比第一条稍微好点
  • 子查询使用索引速度更快

缺点:只适用于id递增的情况

id非递增的情况可以使用以下写法,但这种缺点是分页查询只能放在子查询里面

注意:某些 mysql 版本不支持在 in 子句中使用 limit,所以采用了多个嵌套select

SELECT * FROM `user_operation_log` WHERE id IN (SELECT t.id FROM (SELECT id FROM `user_operation_log` LIMIT 1000000, 10) AS t)
Copier après la connexion
采用 id 限定方式

这种方法要求更高些,id必须是连续递增,而且还得计算id的范围,然后使用 between,sql如下

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

SELECT * FROM `user_operation_log` WHERE id >= 1000000 LIMIT 100
Copier après la connexion

查询结果如下:

sql花费时间
第一条22ms
第二条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
Copier après la connexion

查询结果如下:

sql 花费时间
第一条 15676ms
第二条 7298ms
第三条 15960ms

Les résultats montrent qu'en réduisant les colonnes inutiles, l'efficacité des requêtes peut également être considérablement améliorée.

Les vitesses de la première et de la troisième requête sont presque les mêmes. À ce stade, vous vous plaindrez certainement, alors pourquoi devrais-je. écrivez autant de champs ? , juste * et vous avez terminé

Notez que mon serveur et mon client MySQL sont sur la même machine, donc les données de requête sont similaires. Les étudiants qualifiés peuvent tester le client et MySQL séparément

SELECT * C'est le cas. non Est-ce que ça sent bon ?

Au fait, je voudrais ajouter pourquoi SELECT * devrait être interdit. N'est-ce pas délicieux parce que c'est simple et insensé ?

Deux points principaux :

  1. En utilisant "SELECT *", la base de données doit analyser plus d'objets, de champs, d'autorisations, d'attributs et d'autres contenus associés. Lorsque les instructions SQL sont complexes et qu'il y a de nombreuses analyses difficiles, cela provoquera un problème. lourde charge sur la base de données.
  2. Augmente la surcharge du réseau, * Parfois, des champs de texte inutiles et volumineux tels que log et IconMD5 sont ajoutés par erreur, et la taille de la transmission des données augmentera géométriquement. D'autant plus que MySQL et l'application ne sont pas sur la même machine, cette surcharge est très évidente.

Fin

Enfin, j'espère que vous pourrez le pratiquer vous-même, et vous gagnerez certainement plus !

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:Java后端技术全栈
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal