Pourquoi n'est-il pas recommandé d'utiliser SELECT * ?

"Ne pas utiliser SELECT *" est presque devenu une règle d'or utilisée par MySQL. Même le "Ali Java Development Manual" indique clairement qu'il ne doit pas être utilisé *comme liste de champs de requête, ce qui rend cette règle plus autoritaire.

Manuel de développement Ali Java

Cependant, je l'utilise toujours directement dans le processus de développement pour SELECT *deux raisons :

  1. En raison de sa simplicité, l'efficacité du développement est très élevée et si des champs sont fréquemment ajoutés ou modifiés par la suite, l'instruction SQL n'a pas besoin d'être modifiée ;
  2. Je pense que c'est une mauvaise habitude d'optimiser prématurément, à moins que vous ne puissiez déterminer au départ les champs dont vous avez réellement besoin à la fin et créer des index appropriés pour eux ; sinon, je choisis d'optimiser le SQL lorsque je rencontre des problèmes, bien sûr, le prémisse est que le problème n'est pas fatal.

Mais il faut toujours savoir pourquoi il est déconseillé de l'utiliser directement SELECT *… Cet article donne des raisons sous 4 aspects.

1. E/S disque inutiles

Nous savons que MySQL stocke essentiellement les enregistrements d'utilisateurs sur le disque, donc une opération de requête est un comportement d'E/S de disque (à condition que les enregistrements à interroger ne soient pas mis en cache en mémoire).

Plus il y a de champs interrogés, plus il y a de contenu à lire, ce qui augmente la surcharge d'E/S disque. Surtout lorsque certains champs sont de type TEXT, MEDIUMTEXTou BLOB, etc., l'effet est particulièrement évident.

L'utilisation SELECT *de MySQL consommera-t-elle plus de mémoire ?

Théoriquement non, car pour la couche serveur, au lieu de stocker le jeu de résultats complet en mémoire et de le transmettre immédiatement au client, chaque fois qu'une ligne est obtenue du moteur de stockage, elle est écrite dans un net_bufferespace mémoire appelé, La taille de cette mémoire est contrôlée par des variables système net_buffer_lengthet la valeur par défaut est de 16 Ko ; lorsqu'elle est net_bufferpleine, elle écrit des données dans l'espace mémoire de la pile du réseau local et les socket send bufferenvoie au client. Une fois la transmission réussie (la lecture du client est terminée) , il est vidé net_buffer, puis continue à lire la ligne suivante et à écrire.

C'est-à-dire que, par défaut, l'espace mémoire maximal occupé par le jeu de résultats est uniquement la net_buffer_lengthtaille, et il n'occupera pas d'espace mémoire supplémentaire en raison du plus grand nombre de champs.

2. Augmentez la latence du réseau

Poursuivant le point précédent, bien que socket send bufferles données soient envoyées au client à chaque fois, il semble que la quantité de données ne soit pas importante à la fois, mais il est insupportable que quelqu'un ait utilisé * ou que les champs TEXTdu type soient également découverts, et la quantité totale de données est importante, ce qui entraîne directement une augmentation du nombre de transmissions sur le réseau.MEDIUMTEXTBLOB

Cette surcharge est très visible si MySQL et l'application ne sont pas sur la même machine. Même si le serveur et le client MySQL sont sur la même machine et que le protocole utilisé est toujours TCP, la communication prend plus de temps.

3. Impossible d'utiliser l'index de couverture

Pour illustrer cela, nous devons créer un tableau

CREATE TABLE `user_innodb` (
  `id` int NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `gender` tinyint(1) DEFAULT NULL,
  `phone` varchar(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `IDX_NAME_PHONE` (`name`,`phone`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
复制代码

Nous avons créé une table dont le moteur de stockage est InnoDB user_innodb, l'avons définie comme idclé primaire nameet avons créé un phoneindex conjoint pour et, et enfin initialisé 500W + éléments de données de manière aléatoire dans la table.

InnoDB créera automatiquement idun arbre B + appelé index de clé primaire (également appelé index clusterisé) pour la clé primaire. La caractéristique la plus importante de cet arbre B + est que les nœuds feuilles contiennent des enregistrements utilisateur complets, qui ressemblent à ceci.

index de clé primaire

Si nous exécutons cette instruction

SELECT * FROM user_innodb WHERE name = '蝉沐风';
复制代码

Utilisez pour EXPLAINafficher le plan d'exécution de l'instruction :

On constate que cette instruction SQL utilisera l' IDX_NAME_PHONEindex, qui est un index secondaire. Les nœuds feuilles de l'index secondaire ressemblent à ceci :

nameLe moteur de stockage InnoDB trouvera les enregistrements dans les nœuds feuilles de l'index secondaire en fonction des conditions de recherche 蝉沐风, mais seuls les enregistrements nameet les champs de phoneclé primaire sont enregistrés dans l'index secondaire (qui nous a dit de les utiliser ), donc InnoDB doit prendre la clé primaire pour trouver cet enregistrement complet dans l'index de la clé primaire s'appelle une table de retour .idSELECT *id

想一下,如果二级索引的叶子节点上有我们想要的所有数据,是不是就不需要回表了呢?是的,这就是覆盖索引

举个例子,我们恰好只想搜索namephone以及主键字段。

SELECT id, name,  phone FROM user_innodb WHERE name = "蝉沐风";
复制代码

使用EXPLAIN查看一下语句的执行计划:

可以看到Extra一列显示Using index,表示我们的查询列表以及搜索条件中只包含属于某个索引的列,也就是使用了覆盖索引,能够直接摒弃回表操作,大幅度提高查询效率。

4. 可能拖慢JOIN连接查询

我们创建两张表t1t2进行连接操作来说明接下来的问题,并向t1表中插入了100条数据,向t2中插入了1000条数据。

CREATE TABLE `t1` (
  `id` int NOT NULL,
  `m` int DEFAULT NULL,
  `n` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT;

CREATE TABLE `t2` (
  `id` int NOT NULL,
  `m` int DEFAULT NULL,
  `n` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT;
复制代码

如果我们执行下面这条语句

SELECT * FROM t1 STRAIGHT_JOIN t2 ON t1.m = t2.m;
复制代码

这里我使用了STRAIGHT_JOIN强制令t1表作为驱动表,t2表作为被驱动表

对于连接查询而言,驱动表只会被访问一遍,而被驱动表却要被访问好多遍,具体的访问次数取决于驱动表中符合查询记录的记录条数。由于已经强制确定了驱动表和被驱动表,下面我们说一下两表连接的本质:

  1. t1作为驱动表,针对驱动表的过滤条件,执行对t1表的查询。因为没有过滤条件,也就是获取t1表的所有数据;
  2. 对上一步中获取到的结果集中的每一条记录,都分别到被驱动表中,根据连接过滤条件查找匹配记录

用伪代码表示的话整个过程是这样的:

// t1Res是针对驱动表t1过滤之后的结果集
for (t1Row : t1Res){
  // t2是完整的被驱动表
  for(t2Row : t2){
  	if (满足join条件 && 满足t2的过滤条件){
      发送给客户端
    }  
  }
}
复制代码

这种方法最简单,但同时性能也是最差,这种方式叫做嵌套循环连接(Nested-LoopJoin,NLJ)。怎么加快连接速度呢?

其中一个办法就是创建索引,最好是在被驱动表(t2)连接条件涉及到的字段上创建索引,毕竟被驱动表需要被查询好多次,而且对被驱动表的访问本质上就是个单表查询而已(因为t1结果集定了,每次连接t2的查询条件也就定死了)。

既然使用了索引,为了避免重蹈无法使用覆盖索引的覆辙,我们也应该尽量不要直接SELECT *,而是将真正用到的字段作为查询列,并为其建立适当的索引。

但是如果我们不使用索引,MySQL就真的按照嵌套循环查询的方式进行连接查询吗?当然不是,毕竟这种嵌套循环查询实在是太慢了!

在MySQL8.0之前,MySQL提供了基于块的嵌套循环连接(Block Nested-Loop Join,BLJ)方法,MySQL8.0又推出了hash join方法,这两种方法都是为了解决一个问题而提出的,那就是尽量减少被驱动表的访问次数。

这两种方法都用到了一个叫做join buffer的固定大小的内存区域,其中存储着若干条驱动表结果集中的记录(这两种方法的区别就是存储的形式不同而已),如此一来,把被驱动表的记录加载到内存的时候,一次性和join buffer中多条驱动表中的记录做匹配,因为匹配的过程都是在内存中完成的,所以这样可以显著减少被驱动表的I/O代价,大大减少了重复从磁盘上加载被驱动表的代价。使用join buffer的过程如下图所示:

Diagramme schématique du tampon de jointure

我们看一下上面的连接查询的执行计划,发现确实使用到了hash join(前提是没有为t2表的连接查询字段创建索引,否则就会使用索引,不会使用join buffer)。

Dans le meilleur des cas, il est join buffersuffisamment grand pour contenir tous les enregistrements du jeu de résultats de la table pilotée, de sorte qu'une seule visite à la table pilotée est nécessaire pour terminer l'opération de jointure. Nous pouvons utiliser join_buffer_sizecette variable système pour configurer, la taille par défaut est 256KB. S'il ne peut toujours pas être chargé, placez le jeu de résultats de la table des pilotes dans des lots Une join bufferfois la comparaison dans la mémoire terminée, videz join bufferet chargez le prochain lot de jeux de résultats jusqu'à ce que la connexion soit établie.

Voici le point ! Toutes les colonnes de l'enregistrement de la table des lecteurs ne seront pas insérées join buffer, seules les colonnes de la liste de requêtes et les colonnes des conditions de filtre seront insérées join buffer, alors rappelez-nous encore, il est préférable de ne pas l'utiliser *comme liste de requêtes, juste placez-nous Placez simplement les colonnes qui vous intéressent dans la liste de requêtes, afin de pouvoir y join bufferplacer plus d'enregistrements, réduire le nombre de lots et naturellement réduire le nombre de visites à la table pilotée.

Lecture recommandée

Je suppose que tu aimes

Origine juejin.im/post/7079417143019700255
conseillé
Classement