web-dev-qa-db-fra.com

Comment insérer des valeurs dans une base de données hautement normalisée sans vérification excessive des doublons?

La situation:

Le responsable de notre projet a décidé d'utiliser une base de données hautement normalisée comme conception de notre base de données. Cela signifie que chaque champ d'une grande table est littéralement un ID au lieu de la valeur réelle. Son intention est de ne pas avoir de doublons d'aucune sorte, même dans des endroits où les doublons ne font pas de mal (prénoms des gens, ce genre de choses).

Cela conduit cependant à un problème: lors de l'insertion de nouvelles données, nous devons vérifier chaque sous-table pour voir si la valeur existe (première requête), puis l'insérer si elle ne l'est pas (deuxième requête) ou bien récupérer l'ID, faire ceci pour littéralement chaque colonne de la table principale (donc 30 fois ou quelque chose), puis nous pouvons créer l'objet que nous voulions réellement obtenir. (C'est ~ 60 hits de base de données pour créer un objet!).

Nous travaillons au printemps, nous utilisons donc jdbcTemplate pour réellement construire une connexion à une base de données, et chaque requête coûte cher. Lorsque nous insérons des milliers de nouveaux enregistrements ou les mettons à jour, cela ralentit considérablement la base de données.

Tout ce processus me semble complètement sale et mauvais, et je voulais donc demander: Y a-t-il une meilleure façon? Est-il possible qu'une sous-requête insère une valeur si elle n'existe pas ou non, et renvoie la clé réelle dans les deux cas, qui est utilisée immédiatement pour définir l'ID dans la table principale? Existe-t-il une solution élégante pour réduire le nombre de requêtes sans introduire trop de SQL compliqué (pour les membres de l'équipe)?

6
Joe

Une petite pensée...

Formellement, vous avez quelque chose de similaire à cette structure simplifiée:

CREATE TABLE slave1 (id PK, value UNIQUE);
CREATE TABLE slave2 (id PK, value UNIQUE);
CREATE TABLE main (id PK, id_slave1 FK, id_slave2 FK);

Lorsque vous devez insérer 2 enregistrements (id_1, val_1_1, val_1_2) et (id_2, val_2_1, val_2_2), vous exécutez:

CREATE TEMPORARY TABLE temp (val_slave1, val_slave2) [ENGINE=Memory];

INSERT INTO temp (val_slave1, val_slave2)
VALUES (val_1_1, val_1_2),
       (val_2_1, val_2_2);

INSERT IGNORE INTO slave1 (value)
SELECT DISTINCT val_slave1
FROM temp;

INSERT IGNORE INTO slave2 (value)
SELECT DISTINCT val_slave2
FROM temp;

INSERT INTO main (id_slave1, id_slave2)
SELECT slave1.id, slave2.id
FROM temp
JOIN slave1 ON temp.val_slave1 = slave1.value
JOIN slave2 ON temp.val_slave2 = slave2.value;

Le moteur de temp peut être Memory lorsque la quantité de valeurs insérées est faible et InnoDB ou autre chose si le tableau de données inséré est énorme.

INSERT IGNORE fonctionne assez rapidement sur un champ indexé UNIQUE. Il garantit qu'aucun doublon dans les tables esclaves et que les valeurs qui doivent être insérées existeront dans les esclaves lors de l'insertion dans la table principale.

Et la requête finale doit également être rapide - en particulier lorsque les champs de table temporaires sont également indexés.

Si vous devez insérer un seul enregistrement, vous pouvez bien sûr ne pas utiliser la table temp... mais je pense que l'uniformité est plus sûre qu'une petite simplification.

Bien sûr, cela peut être optimisé. Par exemple, toutes les insertions peuvent être jointes en une seule procédure stockée, et vous n'avez pas besoin de "60 hits de base de données", un seul APPEL suffit. Enfin, vous ne devez exécuter que 3 requêtes indépendantes du nombre d'enregistrements à insérer. Et un seul d'entre eux (insertion dans tentable) peut être énorme (ou même il peut être divisé en beaucoup d'inserts).

3
Akina

Si vous pouvez grouper les données, je peux vous donner une technique qui implique 2 requêtes par lot - une pour insérer tout nouveau nom pour le lot, un pour trouver tous les identifiants. Et cela ne gaspille pas les identifiants, comme REPLACE, INSERT IGNORE, IODKU, etc.

  1. Créez une table temporaire avec toutes les données dénormalisées. Il a des colonnes vacantes pour les identifiants.
  2. Exécutez les 2 requêtes pour chaque colonne qui doit être normalisée. (2 * 30 requêtes dans votre cas).
  3. INSERT .. SELECT .. pour déplacer les données vers les tables réelles.
  4. Tronquez ou supprimez la table temporaire.

Voir Normalisation en masse

Cela a été conçu (et perfectionné il y a des années) lorsque j'avais besoin de pelleter de grandes quantités de données (provenant probablement de nombreux clients) dans des tableaux et qu'une normalisation (non totale) était nécessaire.

Au début, j'avais INSERT IGNORE, mais j'ai rapidement réalisé que je risquais de manquer d'ID auto_increment. Je n'étais pas disposé à utiliser 8 octets BIGINTs. Après tout, l'un des objectifs de la normalisation est d'économiser de l'espace.

0
Rick James