Domanda

Non riesco a capire perché la mia query rallenta.Il tutto si riduce a quattro tabelle:squadra, giocatore, equipaggiamento e metadati.I record relativi a giocatore e equipaggiamento hanno un FK per la squadra, rendendo la squadra genitore del giocatore e dell'attrezzatura.E tutte e tre le righe di queste tabelle hanno ciascuna un record nei metadati che memorizza elementi come la data di creazione, l'ID utente del creatore, ecc.

Ciò che vorrei recuperare tutto in una volta sono tutti i record di giocatori e attrezzature che appartengono a una particolare squadra, in ordine di data di creazione.Parto dalla tabella dei metadati e mi unisco a sinistra alle tabelle dei giocatori e dell'attrezzatura tramite l'FK metadata_id, ma quando provo a filtrare SELECT per recuperare solo i record per una determinata squadra, la query rallenta molto quando ci sono molte righe.

Ecco la domanda:

SELECT metadata.creation_date, player.id, equipment.id
FROM
  metadata
  JOIN datatype       ON datatype.id           = metadata.datatype_id
  LEFT JOIN player    ON player.metadata_id    = metadata.id
  LEFT JOIN equipment ON equipment.metadata_id = metadata.id
WHERE
  datatype.name IN ('player', 'equipment')
  AND (player.team_id = 1 OR equipment.team_id = 1)
ORDER BY metadata.creation_date;

Dovrai aggiungere molte righe per vedere davvero il rallentamento, circa 10.000 per ogni tabella.Quello che non capisco è perché è davvero veloce se filtro solo la clausola where su una tabella, ad esempio:"...AND player.team_id = 1" Ma quando aggiungo l'altro per farlo "...AND (player.team_id = 1 OR Equipment.team_id = 1)" ci vuole molto, molto più tempo.

Ecco le tabelle e i tipi di dati.Tieni presente che una cosa che sembra aiutare molto, ma non molto, sono le chiavi combinate su giocatore ed equipaggiamento per metadata_id e team_id.

CREATE TABLE `metadata` (
  `id` INT(4) unsigned NOT NULL auto_increment,
  `creation_date` DATETIME NOT NULL,
  `datatype_id` INT(4) unsigned NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB;
CREATE TABLE `datatype` (
  `id` INT(4) unsigned NOT NULL auto_increment,
  `name` VARCHAR(255) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB;
CREATE TABLE `team` (
  `id` INT(4) unsigned NOT NULL auto_increment,
  `metadata_id` INT(4) unsigned NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB;
CREATE TABLE `player` (
  `id` INT(4) unsigned NOT NULL auto_increment,
  `metadata_id` INT(4) unsigned NOT NULL,
  `team_id` INT(4) unsigned NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB;
CREATE TABLE `equipment` (
  `id` INT(4) unsigned NOT NULL auto_increment,
  `metadata_id` INT(4) unsigned NOT NULL,
  `team_id` INT(4) unsigned NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB;
ALTER TABLE  `metadata` ADD INDEX (  `datatype_id` ),
  ADD INDEX ( `creation_date` );
ALTER TABLE  `team`      ADD INDEX (  `metadata_id` );
ALTER TABLE  `player`    ADD INDEX `metadata_id` (  `metadata_id`,  `team_id` ),
  ADD INDEX ( `team_id` );
ALTER TABLE  `equipment` ADD INDEX `metadata_id` (  `metadata_id`,  `team_id` ),
  ADD INDEX ( `team_id` );
ALTER TABLE `metadata`  ADD CONSTRAINT `metadata_ibfk_1`  FOREIGN KEY (`datatype_id`) REFERENCES `datatype` (`id`);
ALTER TABLE `team`      ADD CONSTRAINT `team_ibfk_1`      FOREIGN KEY (`metadata_id`) REFERENCES `metadata` (`id`);
ALTER TABLE `player`    ADD CONSTRAINT `player_ibfk_1`    FOREIGN KEY (`metadata_id`) REFERENCES `metadata` (`id`);
ALTER TABLE `player`    ADD CONSTRAINT `player_ibfk_2`    FOREIGN KEY (`team_id`)     REFERENCES `team` (`id`);
ALTER TABLE `equipment` ADD CONSTRAINT `equipment_ibfk_1` FOREIGN KEY (`metadata_id`) REFERENCES `metadata` (`id`);
ALTER TABLE `equipment` ADD CONSTRAINT `equipment_ibfk_2` FOREIGN KEY (`team_id`)     REFERENCES `team` (`id`);
INSERT INTO `datatype` VALUES(1,'team'),(2,'player'),(3,'equipment');

notare che che mi rendo conto che potrei facilmente accelerare il processo eseguendo un'UNION di due SELECTS sul giocatore e sull'equipaggiamento per un dato ID di squadra, ma l'ORM che sto utilizzando non supporta nativamente le UNION e quindi preferirei di gran lunga provare a vedere se riesco può invece ottimizzare questa query.Inoltre sono semplicemente curioso.

È stato utile?

Soluzione

In MySQL è difficile ottimizzare "OR" condizioni.

Un rimedio comune è dividere la query in due query più semplici e utilizzare UNION per combinarli.

 (SELECT metadata.creation_date, datatype.name, player.id
  FROM metadata
    JOIN datatype ON datatype.id = metadata.datatype_id
    JOIN player ON player.metadata_id = metadata.id
  WHERE datatype.name = 'player' AND player.team_id = 1)
 UNION ALL
 (SELECT metadata.creation_date, datatype.name, equipment.id
  FROM metadata
    JOIN datatype ON datatype.id = metadata.datatype_id
    JOIN equipment ON equipment.metadata_id = metadata.id
  WHERE datatype.name = 'equipment' AND equipment.team_id = 1)
 ORDER BY creation_date;

Devi usare le parentesi in modo che il ORDER BY si applica al risultato di UNION invece che solo al risultato del secondo SELECT.


aggiornamento: Quello che stai facendo si chiama Associazioni Polimorfiche ed è difficile da usare in SQL.Lo chiamo addirittura un antipattern SQL, nonostante alcuni framework ORM ne incoraggino l'utilizzo.

Ciò che realmente abbiamo in questo caso è una relazione tra squadre e giocatori, e tra squadre e attrezzature.I giocatori non sono attrezzature e le attrezzature non sono giocatori;non hanno un supertipo comune.È fuorviante sia in senso OO che in senso relazionale che tu li abbia modellati in questo modo.

Direi di scaricare il tuo metadata E datatype tavoli.Queste sono strutture antirelazionali.Utilizzare invece il file team_id (che presumo sia una chiave esterna per a teams tavolo).Tratta i giocatori e l'equipaggiamento come tipi distinti.Recuperateli separatamente se non potete usarli UNION nel tuo ORM.Quindi combina i set di risultati nella tua applicazione.

Non è necessario recuperare tutto in una singola query SQL.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top