Pourquoi les jointures sont-elles mauvaises lorsque l'on considère l'évolutivité?


92

Pourquoi les jointures sont-elles mauvaises ou «lentes». Je sais que j'ai entendu cela plus d'une fois. J'ai trouvé cette citation

Le problème est que les jointures sont relativement lentes, en particulier sur de très grands ensembles de données, et si elles sont lentes, votre site Web est lent. Il faut beaucoup de temps pour extraire toutes ces informations séparées du disque et les rassembler à nouveau.

la source

J'ai toujours pensé qu'ils étaient rapides, surtout lorsqu'ils recherchaient un PK. Pourquoi sont-ils «lents»?

sql  join 

Réponses:


98

L'évolutivité consiste à pré-calculer, à répartir ou à réduire le travail répété à l'essentiel, afin de minimiser l'utilisation des ressources par unité de travail. Pour bien évoluer, vous ne faites rien dont vous n'avez pas besoin en volume, et vous vous assurez que les choses que vous faites sont faites aussi efficacement que possible.

Dans ce contexte, bien sûr, joindre deux sources de données distinctes est relativement lent, du moins par rapport au fait de ne pas les joindre, car c'est un travail que vous devez faire en direct au moment où l'utilisateur le demande.

Mais rappelez-vous que l'alternative est de ne plus avoir du tout deux éléments de données séparés; vous devez mettre les deux points de données disparates dans le même enregistrement. Vous ne pouvez pas combiner deux données différentes sans conséquence quelque part, alors assurez-vous de bien comprendre le compromis.

La bonne nouvelle est que les bases de données relationnelles modernes sont bonnes pour les jointures. Vous ne devriez pas vraiment penser aux jointures comme lentes avec une bonne base de données bien utilisée. Il existe un certain nombre de moyens d'évolutivité pour prendre des jointures brutes et les rendre beaucoup plus rapides:

  • Rejoignez sur une clé de substitution (autonumer / colonne d'identité) plutôt que sur une clé naturelle. Cela signifie des comparaisons plus petites (et donc plus rapides) pendant l'opération de jointure
  • Index
  • Vues matérialisées / indexées (considérez cela comme une jointure pré-calculée ou une dénormalisation gérée )
  • Colonnes calculées. Vous pouvez l'utiliser pour hacher ou pré-calculer les colonnes clés d'une jointure, de sorte que ce qui serait une comparaison compliquée pour une jointure est maintenant beaucoup plus petit et potentiellement pré-indexé.
  • Partitions de table (aide avec de grands ensembles de données en répartissant la charge sur plusieurs disques, ou en limitant ce qui aurait pu être une analyse de table à une analyse de partition)
  • OLAP (pré-calcule les résultats de certains types de requêtes / jointures. Ce n'est pas tout à fait vrai, mais vous pouvez considérer cela comme une dénormalisation générique )
  • Réplication, groupes de disponibilité, envoi de journaux ou autres mécanismes permettant à plusieurs serveurs de répondre aux requêtes de lecture pour la même base de données, et ainsi de redimensionner votre charge de travail entre plusieurs serveurs.
  • Utilisation d'une couche de mise en cache comme Redis pour éviter de réexécuter des requêtes nécessitant des jointures complexes.

J'irais jusqu'à dire que la principale raison pour laquelle les bases de données relationnelles existent est de vous permettre de faire des jointures efficacement * . Ce n'est certainement pas seulement pour stocker des données structurées (vous pouvez le faire avec des constructions de fichier plat comme csv ou xml). Certaines des options que j'ai énumérées vous permettront même de créer complètement votre jointure à l'avance, de sorte que les résultats sont déjà faits avant d'émettre la requête - tout comme si vous aviez dénormalisé les données (certes au prix d'opérations d'écriture plus lentes).

Si vous avez une jointure lente, vous n'utilisez probablement pas correctement votre base de données.

La dénormalisation ne doit être effectuée qu'après l'échec de ces autres techniques. Et la seule façon de vraiment juger de «l'échec» est de fixer des objectifs de performance significatifs et de les mesurer par rapport à ces objectifs. Si vous n'avez pas mesuré, il est trop tôt pour même penser à la dénormalisation.

* Autrement dit, existent en tant qu'entités distinctes de simples collections de tables. Une raison supplémentaire pour un vrai rdbms est l'accès simultané sécurisé.


14
Les index devraient probablement être en haut de la liste. Beaucoup de développeurs (qui toussent ) semblent les oublier lorsqu'ils testent sur un petit ensemble de données, puis mettent la base de données à genoux en production. J'ai vu des requêtes qui s'exécutent à l'ordre de 100 000 fois plus vite simplement en ajoutant des index. Et ce sont des index arbitraires sans même effectuer une analyse approfondie des données pour déterminer le meilleur mélange pour la correspondance des préfixes les plus à gauche.
Duncan

Je pense que j'ai l'ordre correct - c'est juste que la plupart des développeurs font déjà le premier élément, et les index sont donc le premier élément où ils devront apporter des modifications.
Joel Coehoorn

Dans votre troisième élément, vous mentionnez "Vues matérialisées / indexées". Parlez-vous de vues SQL régulières, ou autre chose?
slolife

Les vues SQL standard @slolife sont comme exécuter une requête supplémentaire en arrière-plan à la volée lorsque vous utilisez une requête qui fait référence à la vue. Mais vous pouvez aussi dire au serveur SQL de "matérialiser" certaines vues. Lorsque vous faites cela, le serveur sql conservera une copie supplémentaire des données de la vue, tout comme une table normale, de sorte que lorsque vous référencez la vue dans une requête, il n'a plus à exécuter cette requête en arrière-plan car les données sont déjà là . Vous pouvez également placer des index différents sur la vue de la table source, pour vous aider davantage à optimiser les performances.
Joel Coehoorn

Merci Joel. Je vais devoir examiner cela.
slolife

29

Les jointures peuvent être plus lentes que de les éviter par la dénormalisation, mais si elles sont utilisées correctement (jointure sur des colonnes avec des index appropriés, etc.), elles ne sont pas intrinsèquement lentes .

La dé-normalisation est l'une des nombreuses techniques d'optimisation que vous pouvez envisager si votre schéma de base de données bien conçu présente des problèmes de performances.


2
... sauf dans MySQL, qui semble avoir des problèmes de performances avec un grand nombre de jointures quelle que soit l'apparence de vos index. Ou du moins, dans le passé.
Powerlord

2
Point pris, s'il y a des problèmes connus avec le SGBD spécifique (et peut-être même la version), ce conseil peut avoir du sens, mais en tant que conseil général, il est assez trompeur si vous utilisez une base de données relationnelle. Cela dit, les mécanismes de stockage non relationnels deviennent de plus en plus populaires SimpleDB et CouchDB d' Amazon ( couchdb.apache.org ) en sont des exemples. Si vous êtes mieux servi en laissant le modèle relationnel derrière vous, vous devriez probablement laisser les produits optimisés pour l'arrière-plan aussi et chercher d'autres outils.
Tendayi Mawushe

13

l'article dit qu'ils sont lents par rapport à l'absence de jointures. ceci peut être réalisé avec la dénormalisation. il y a donc un compromis entre vitesse et normalisation. n'oubliez pas l'optimisation prématurée également :)


même ce n'est pas une règle stricte, si vous joignez sur une table, mysql peut utiliser un index pour effectuer cette jointure - cette jointure d'index pourrait élaguer de nombreuses lignes, et un autre index pour toute clause where sur les tables. Si vous ne rejoignez pas, mysql n'utilisera généralement qu'un seul index (ce qui n'est peut-être pas le plus efficace), peu importe comment votre clause where est formée.
leeeroy

11

Tout d'abord, la raison d'être d'une base de données relationnelle (raison d'être) est de pouvoir modéliser les relations entre entités. Les jointures sont simplement les mécanismes par lesquels nous traversons ces relations. Ils ont certainement un coût minime, mais sans jointures, il n'y a vraiment aucune raison d'avoir une base de données relationnelle.

Dans le monde académique, nous apprenons des choses comme les différentes formes normales (1ère, 2ème, 3ème, Boyce-Codd, etc.), et nous apprenons différents types de clés (primaires, étrangères, alternatives, uniques, etc.) et comment ces éléments s'emboîtent pour concevoir une base de données. Et nous apprenons les rudiments de SQL ainsi que la manipulation de la structure et des données (DDL et DML).

Dans le monde de l'entreprise, bon nombre des constructions académiques se révèlent nettement moins viables qu'on ne le pensait. Un exemple parfait est la notion de clé primaire. Sur le plan académique, c'est cet attribut (ou collection d'attributs) qui identifie de manière unique une ligne dans la table. Ainsi, dans de nombreux domaines problématiques, la clé primaire académique appropriée est un composite de 3 ou 4 attributs. Cependant, presque tout le monde dans le monde de l'entreprise moderne utilise un entier séquentiel généré automatiquement comme clé primaire d'une table. Pourquoi? Deux raisons. La première est que cela rend le modèle beaucoup plus propre lorsque vous migrez des FK partout. La deuxième, et la plus pertinente pour cette question, est que la récupération de données via des jointures est plus rapide et plus efficace sur un seul entier que sur 4 colonnes varchar (comme déjà mentionné par quelques personnes).

Explorons un peu plus en profondeur deux sous-types spécifiques de bases de données du monde réel. Le premier type est une base de données transactionnelle. C'est la base de nombreuses applications de commerce électronique ou de gestion de contenu qui pilotent des sites modernes. Avec une base de données de transaction, vous optimisez fortement vers le «débit de transaction». La plupart des applications de commerce ou de contenu doivent équilibrer les performances des requêtes (à partir de certaines tables) avec les performances d'insertion (dans d'autres tables), bien que chaque application ait ses propres problèmes commerciaux uniques à résoudre.

Le deuxième type de base de données du monde réel est une base de données de rapports. Ceux-ci sont utilisés presque exclusivement pour agréger des données commerciales et pour générer des rapports commerciaux significatifs. Ils ont généralement une forme différente de celle des bases de données de transaction où les données sont générées et ils sont hautement optimisés pour la vitesse de chargement des données en masse (ETL) et les performances des requêtes avec des ensembles de données volumineux ou complexes.

Dans chaque cas, le développeur ou l'administrateur de base de données doit soigneusement équilibrer les courbes de fonctionnalité et de performance, et il existe de nombreuses astuces d'amélioration des performances des deux côtés de l'équation. Dans Oracle, vous pouvez faire ce qu'on appelle un "plan d'explication" afin de voir spécifiquement comment une requête est analysée et exécutée. Vous cherchez à maximiser l'utilisation correcte des index par la base de données. Un non-non vraiment désagréable est de mettre une fonction dans la clause where d'une requête. Chaque fois que vous faites cela, vous garantissez qu'Oracle n'utilisera aucun index sur cette colonne particulière et vous verrez probablement une analyse de table complète ou partielle dans le plan d'explication. Ce n'est qu'un exemple spécifique de la façon dont une requête pourrait être écrite qui finit par être lente, et cela n'a rien à voir avec les jointures.

Et tandis que nous parlons d'analyses de table, elles ont évidemment un impact sur la vitesse de requête proportionnellement à la taille de la table. Une analyse complète de la table de 100 lignes n'est même pas perceptible. Exécutez cette même requête sur une table avec 100 millions de lignes, et vous devrez revenir la semaine prochaine pour le retour.

Parlons de normalisation pendant une minute. C'est un autre sujet académique largement positif qui peut être trop stressé. La plupart du temps, lorsque nous parlons de normalisation, nous entendons vraiment l'élimination des données en double en les plaçant dans sa propre table et en migrant un FK. Les gens ignorent généralement toute la question de la dépendance décrite par 2NF et 3NF. Et pourtant, dans un cas extrême, il est certainement possible d'avoir une base de données BCNF parfaite, énorme et une bête complète contre laquelle écrire du code parce qu'elle est tellement normalisée.

Alors, où équilibrons-nous? Il n'y a pas de meilleure réponse. Toutes les meilleures réponses ont tendance à être un compromis entre la facilité de maintenance de la structure, la facilité de maintenance des données et la facilité de création / maintenance de code. En général, moins il y a de duplication de données, mieux c'est.

Alors pourquoi les jointures sont-elles parfois lentes? Parfois, c'est une mauvaise conception relationnelle. Parfois, c'est une indexation inefficace. Parfois, c'est un problème de volume de données. Parfois, c'est une requête horriblement écrite.

Désolé pour une réponse aussi longue, mais je me suis senti obligé de fournir un contexte plus charnu autour de mes commentaires plutôt que de simplement déclencher une réponse à 4 points.


10

Les personnes ayant des bases de données de taille terrabyte utilisent toujours des jointures, si elles peuvent les faire fonctionner en termes de performances, vous le pouvez également.

Il y a de nombreuses raisons de ne pas dénomaliser. Premièrement, la vitesse des requêtes de sélection n'est pas la seule ni même la principale préoccupation des bases de données. L'intégrité des données est la première préoccupation. Si vous dénormalisez, vous devez mettre en place des techniques pour conserver les données dénormalisées à mesure que les données parentes changent. Supposons donc que vous preniez pour stocker le nom du client dans toutes les tables au lieu de rejoindre la table client sur le client_Id. Désormais, lorsque le nom du client change (100% de chances que certains des noms de clients changent au fil du temps), vous devez maintenant mettre à jour tous les enregistrements enfants pour refléter ce changement. Si vous faites cela avec une mise à jour en cascade et que vous avez un million d'enregistrements enfants, à quelle vitesse pensez-vous que cela va être et combien d'utilisateurs vont souffrir de problèmes de verrouillage et de retards dans leur travail pendant que cela se produit? En outre, la plupart des gens qui dénormalisent parce que "

La dénormalisation est un processus complexe qui nécessite une compréhension approfondie des performances et de l'intégrité de la base de données pour être effectuée correctement. N'essayez pas de dénormaliser à moins d'avoir une telle expertise au sein du personnel.

Les jointures sont assez rapides si vous faites plusieurs choses. Utilisez d'abord une clé suggérée, une jointure int est presque toujours la jointure la plus rapide. Deuxièmement, indexez toujours la clé étrangère. Utilisez des tables dérivées ou des conditions de jointure pour créer un ensemble de données plus petit sur lequel filtrer. Si vous avez une grande base de données très complexe, engagez un spécialiste des bases de données expérimenté dans le partage et la gestion d'énormes bases de données. Il existe de nombreuses techniques pour améliorer les performances sans se débarrasser des jointures.

Si vous avez juste besoin d'une capacité de requête, alors oui, vous pouvez concevoir un entrepôt de données qui peut être dénormalisé et alimenté via un outil ETL (optimisé pour la vitesse) et non la saisie de données utilisateur.


8

Les jointures sont lentes si

  • les données sont mal indexées
  • résultats mal filtrés
  • requête de jonction mal écrite
  • ensembles de données très volumineux et complexes

Donc, vrai, plus vos ensembles de données sont volumineux, plus vous aurez besoin de traitement pour une requête, mais vérifier et travailler sur les trois premières options de ce qui précède donnera souvent d'excellents résultats.

Votre source propose la dénormalisation en option. Cela ne fonctionne que tant que vous avez épuisé les meilleures alternatives.


7

Les jointures peuvent être lentes si de grandes parties d'enregistrements de chaque côté doivent être analysées.

Comme ça:

SELECT  SUM(transaction)
FROM    customers
JOIN    accounts
ON      account_customer = customer_id

Même si un index est défini sur account_customer, tous les enregistrements de ce dernier doivent encore être analysés.

Pour la liste de requêtes, les optimiseurs décents ne prendront probablement même pas en compte le chemin d'accès à l'index, en faisant un HASH JOINou un à la MERGE JOINplace.

Notez que pour une requête comme celle-ci:

SELECT  SUM(transaction)
FROM    customers
JOIN    accounts
ON      account_customer = customer_id
WHERE   customer_last_name = 'Stellphlug'

la jointure sera très probablement rapide: d'abord, un index sur customer_last_namesera utilisé pour filtrer tous les Stellphlug (qui sont bien sûr peu nombreux), puis un scan d'index sur account_customersera émis pour chaque Stellphlug pour trouver ses transactions.

Malgré le fait qu'il peut s'agir de milliards d'enregistrements dans accountset customers, seuls quelques-uns devront réellement être numérisés.


mais il est difficile de l'éviter. concevez votre application pour que ce type de requêtes ne soit pas exécuté trop souvent.
Andrey

1
Si un index est défini sur la accounts(account_customer)plupart des SGBDR, il utilisera cet index pour savoir exactement quelles lignes de la customersbase de données doivent être analysées.
jemfinch

oui, mais ce n'est pas une opération bon marché de toute façon. vous pouvez stocker la somme dans un champ et la mettre à jour à chaque transaction.
Andrey

@jemfinch: non, ils ne le feront pas. Cela nécessiterait d'analyser l'intégralité de l'index juste pour filtrer les clients, puis de scanner l'index du client dans une boucle imbriquée. A HASH JOINserait beaucoup plus rapide, c'est donc ce qui sera utilisé sauf dans toutes les principales bases de données sauf MySQL, qui ne fera que customersmener une boucle imbriquée (car sa taille est plus petite)
Quassnoi

4

Joins are fast.Les jointures doivent être considérées comme une pratique standard avec un schéma de base de données correctement normalisé. Les jointures vous permettent de rejoindre des groupes de données disparates de manière significative. N'ayez pas peur de la jointure.

La mise en garde est que vous devez comprendre la normalisation, la jonction et l'utilisation correcte des index.

Méfiez-vous de l'optimisation prématurée, car le principal échec de tous les projets de développement est de respecter les délais. Une fois que vous avez terminé le projet et que vous comprenez les compromis, vous pouvez enfreindre les règles si vous pouvez le justifier.

Il est vrai que les performances de jointure se dégradent de manière non linéaire à mesure que la taille de l'ensemble de données augmente. Par conséquent, il ne s'adapte pas aussi bien que les requêtes de table unique, mais il évolue toujours.

Il est également vrai qu'un oiseau vole plus vite sans ailes, mais seulement tout droit.


3

Les jointures nécessitent un traitement supplémentaire car elles doivent rechercher plus de fichiers et plus d'index pour «joindre» les données ensemble. Cependant, les «très grands ensembles de données» sont tous relatifs. Qu'elle est la définition de grand? Dans le cas des JOINs, je pense que c'est une référence à un grand ensemble de résultats, pas à cet ensemble de données global.

La plupart des bases de données peuvent traiter très rapidement une requête qui sélectionne 5 enregistrements dans une table principale et joint 5 enregistrements à partir d'une table associée pour chaque enregistrement (en supposant que les index corrects sont en place). Ces tables peuvent contenir des centaines de millions d'enregistrements chacune, voire des milliards.

Une fois que votre jeu de résultats commence à grandir, les choses vont ralentir. En utilisant le même exemple, si la table principale aboutit à 100 000 enregistrements, alors il y aura 500 000 enregistrements «joints» à trouver. Il suffit d'extraire autant de données de la base de données avec des retards supplémentaires.

N'évitez pas les JOIN, sachez simplement que vous devrez peut-être optimiser / dénormaliser lorsque les ensembles de données deviennent "très volumineux".


3

Également extrait de l'article que vous avez cité:

De nombreux sites Web à grande échelle avec des milliards d'enregistrements, des pétaoctets de données, des milliers d'utilisateurs simultanés et des millions de requêtes par jour utilisent un schéma de partitionnement et certains préconisent même la dénormalisation comme la meilleure stratégie pour l'architecture du niveau de données.

et

Et à moins que vous ne soyez un très grand site Web, vous n'avez probablement pas à vous soucier de ce niveau de complexité.

et

C'est plus sujet aux erreurs que de laisser la base de données faire tout ce travail, mais vous êtes capable de faire évoluer au-delà de ce que même les bases de données les plus sophistiquées peuvent gérer.

L'article traite de méga-sites comme Ebay. À ce niveau d'utilisation, vous devrez probablement envisager autre chose que la gestion de base de données relationnelle simple. Mais dans le cours «normal» des affaires (applications avec des milliers d'utilisateurs et des millions d'enregistrements), les approches les plus coûteuses et les plus sujettes aux erreurs sont exagérées.


2

Les jointures sont considérées comme une force opposée à l'évolutivité, car elles constituent généralement le goulot d'étranglement et ne peuvent pas être facilement distribuées ou mises en parallèle.


Je ne suis pas sûr que ce soit vrai. Je sais que Teradata est certainement capable de distribuer des jointures parmi les amplis. Évidemment, certains types de jointures peuvent être plus délicats / insolubles que d'autres.
Cade Roux

les index peuvent être partitionnés dans le SGBDR allant de mysql à oracle. AFAIK qui évolue (est distribué et peut être mis en parallèle).
Unreason

2

Des tableaux correctement conçus contenant les indices appropriés et des requêtes correctement écrites ne sont pas toujours lents. Où que vous ayez entendu ça:

Pourquoi les jointures sont-elles mauvaises ou `` lentes ''

n'a aucune idée de ce dont ils parlent !!! La plupart des jointures seront très rapides. Si vous devez joindre plusieurs lignes à la fois, vous risquez de prendre un coup par rapport à une table dénormalisée, mais cela revient à Tables correctement conçues, sachez quand dénormaliser et quand ne pas le faire. dans un système de reporting lourd, divisez les données dans des tableaux dénormalisés pour les rapports, ou même créez un entrepôt de données. Dans un système transactionnel lourd, normalisez les tables.


1

La quantité de données temporaires générées peut être énorme en fonction des jointures.

Par exemple, une base de données ici au travail avait une fonction de recherche générique où tous les champs étaient facultatifs. La routine de recherche a effectué une jointure sur chaque table avant le début de la recherche. Cela a bien fonctionné au début. Mais, maintenant que la table principale a plus de 10 millions de lignes ... pas tellement. Les recherches prennent maintenant 30 minutes ou plus.

J'ai été chargé d'optimiser la procédure de recherche stockée.

La première chose que j'ai faite a été si l'un des champs de la table principale était recherché, j'ai fait une sélection dans une table temporaire sur ces champs uniquement. ALORS, j'ai joint toutes les tables avec cette table temporaire avant de faire le reste de la recherche. Les recherches dans lesquelles l'un des champs de la table principale prennent désormais moins de 10 secondes.

Si aucun des champs de la table principale n'est recherché, je fais des optimisations similaires pour d'autres tables. Lorsque j'ai terminé, aucune recherche ne prend plus de 30 secondes avec la plupart sous 10.

L'utilisation du processeur du serveur SQL a également diminué.


@BoltBait: Le message à retenir est-il que vous devriez toujours essayer de réduire le nombre de lignes avant d'effectuer une jointure?
unutbu

Cela a certainement fait des merveilles dans mon cas. Mais je n'optimiserais pas un système tant qu'il ne deviendrait pas nécessaire.
BoltBait

normalement aucune donnée temporaire n'est générée sur les jointures (en fonction bien sûr de la sélectivité, de la mémoire disponible et de la taille des tampons de jointure), AFAIK; cependant, les données temporaires sont généralement créées sur ordre et distinctes s'il n'y a pas d'index pouvant être utilisé pour de telles opérations.
Unreason

1

Alors que les jointures (probablement dues à une conception normalisée) peuvent évidemment être plus lentes pour la récupération de données qu'une lecture à partir d'une seule table, une base de données dénormalisée peut être lente pour les opérations de création / mise à jour de données puisque l'empreinte de la transaction globale ne sera pas minimale.

Dans une base de données normalisée, une donnée ne vivra qu'à un seul endroit, de sorte que l'empreinte d'une mise à jour sera aussi minime que possible. Dans une base de données dénormalisée, il est possible que la même colonne dans plusieurs lignes ou entre les tables doive être mise à jour, ce qui signifie que l'empreinte serait plus grande et que le risque de verrous et de blocages peut augmenter.


1

Eh bien, oui, la sélection de lignes dans une table dénormalisée (en supposant des index décents pour votre requête) peut être plus rapide que la sélection de lignes construites à partir de la jointure de plusieurs tables, en particulier si les jointures ne disposent pas d'index efficaces.

Les exemples cités dans l'article - Flickr et eBay - sont des cas exceptionnels de l'OMI, donc ont (et méritent) des réponses exceptionnelles. L'auteur évoque spécifiquement le manque de RI et l'étendue de la duplication des données dans l'article.

La plupart des applications - encore une fois, IMO - bénéficient de la validation et de la réduction des doublons fournis par les SGBDR.


0

Ils peuvent être lents s'ils sont faits avec négligence. Par exemple, si vous faites un 'select *' sur une jointure, vous prendrez probablement un certain temps pour récupérer les choses. Cependant, si vous choisissez soigneusement les colonnes à renvoyer à partir de chaque table et avec les index appropriés en place, il ne devrait y avoir aucun problème.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.