Domanda

Io sono un parente novizio quando si tratta di banche dati.Stiamo usando MySQL e sto attualmente cercando di accelerare un'istruzione SQL che sembra prendere un po ' per l'esecuzione.Mi guardai intorno in MODO che per una domanda simile ma non ho trovato uno.

L'obiettivo è quello di rimuovere tutte le righe nella tabella a, che hanno un id corrispondente nella tabella B.

Attualmente sto facendo il seguente:

DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE b.id = a.id);

Ci sono circa 100K righe nella tabella a e circa 22K righe della tabella b.La colonna 'id' è il PK per entrambe le tabelle.

Questa affermazione richiede circa 3 minuti per eseguire il mio test di box - Pentium D, XP SP3, 2GB di ram, MySQL 5.0.67.Questo sembra lento per me.Magari no, ma speravo di accelerare le cose.C'è un migliore/il modo più veloce per ottenere questo risultato?


EDIT:

Alcune informazioni aggiuntive che potrebbero essere utili.Le tabelle A e B hanno la stessa struttura, come ho fatto per creare la tabella B:

CREATE TABLE b LIKE a;

Tabella a (e, quindi, la tabella b) ha un paio di indici per velocizzare le query che sono contro di essa.Di nuovo, io sono un parente novizio al DB di lavoro e di apprendimento.Non so quanto di un effetto, se del caso, questo ha sulle cose.Presumo che non hanno un effetto, come gli indici devono essere puliti, giusto?Anch'io mi chiedevo se c'erano altri DB impostazioni che possono influenzare la velocità.

Inoltre, io sono con INNO DB.


Ecco alcune informazioni aggiuntive che potrebbero essere utili a voi.

Tabella A ha una struttura simile a questo (ho sterilizzata questo un po'):

DROP TABLE IF EXISTS `frobozz`.`a`;
CREATE TABLE  `frobozz`.`a` (
  `id` bigint(20) unsigned NOT NULL auto_increment,
  `fk_g` varchar(30) NOT NULL,
  `h` int(10) unsigned default NULL,
  `i` longtext,
  `j` bigint(20) NOT NULL,
  `k` bigint(20) default NULL,
  `l` varchar(45) NOT NULL,
  `m` int(10) unsigned default NULL,
  `n` varchar(20) default NULL,
  `o` bigint(20) NOT NULL,
  `p` tinyint(1) NOT NULL,
  PRIMARY KEY  USING BTREE (`id`),
  KEY `idx_l` (`l`),
  KEY `idx_h` USING BTREE (`h`),
  KEY `idx_m` USING BTREE (`m`),
  KEY `idx_fk_g` USING BTREE (`fk_g`),
  KEY `fk_g_frobozz` (`id`,`fk_g`),
  CONSTRAINT `fk_g_frobozz` FOREIGN KEY (`fk_g`) REFERENCES `frotz` (`g`)
) ENGINE=InnoDB AUTO_INCREMENT=179369 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

Ho il sospetto che parte del problema è che ci sono un certo numero di indici per questa tabella.La tabella B è simile alla tabella B, anche se contiene solo le colonne id e h.

Inoltre, la profilazione risultati sono come segue:

starting 0.000018
checking query cache for query 0.000044
checking permissions 0.000005
Opening tables 0.000009
init 0.000019
optimizing 0.000004
executing 0.000043
end 0.000005
end 0.000002
query end 0.000003
freeing items 0.000007
logging slow query 0.000002
cleaning up 0.000002

RISOLTO

Grazie a tutti per le risposte e i commenti.Certamente mi ha fatto pensare al problema.Complimenti a dotjoe per ottenere me a passo di distanza da il problema chiedendo una semplice domanda: "che Fare con altre tabelle di riferimento una.id?"

Il problema era che c'era un TRIGGER sulla tabella A, che chiama una stored procedure per aggiornare altre due tabelle, C e D.Tabella C era un FK indietro di una.id e dopo aver fatto alcune cose relative a id nella stored procedure, aveva l'istruzione,

DELETE FROM c WHERE c.id = theId;

Ho guardato nelle SPIEGARE istruzione e riscritto questo come,

EXPLAIN SELECT * FROM c WHERE c.other_id = 12345;

Così, ho potuto vedere che questo stava facendo e mi ha dato le seguenti informazioni:

id            1
select_type   SIMPLE
table         c
type          ALL
possible_keys NULL
key           NULL
key_len       NULL
ref           NULL
rows          2633
Extra         using where

Questo mi ha detto che era una dolorosa operazione da fare e dato che stava per ottenere chiamato 22500 volte (per un dato insieme di dati che vengono cancellati), che era il problema.Una volta ho creato un INDICE other_id colonna e rieseguita la spiego, ho ottenuto:

id            1
select_type   SIMPLE
table         c
type          ref
possible_keys Index_1
key           Index_1
key_len       8
ref           const
rows          1
Extra         

Molto meglio, in fatto di veramente grande.

Ho aggiunto che Index_1 e la mia eliminare i tempi sono in linea con i tempi riportati da mattkemp.Questo è davvero un sottile errore da parte mia a causa di scarpa horning alcune funzionalità aggiuntive all'ultimo minuto.Si è scoperto che la maggior parte delle alternative proposte ELIMINARE/istruzioni SELECT, come Daniel indicato, ha finito per prendere essenzialmente la stessa quantità di tempo e come soulmerge detto, che la dichiarazione è stata praticamente meglio che stavo per essere in grado di costruire basato su quello che dovevo fare.Una volta ho fornito un indice per quest'altra tabella C, i miei Elimina veloce.

Post-mortem:
Due lezioni apprese è venuto fuori di questo esercizio.Primo, è chiaro che io non sfruttare la potenza di SPIEGARE dichiarazione per ottenere una migliore idea dell'impatto che la mia query SQL.Che è un rookie errore, quindi non ho intenzione di battere me stesso su quello.Io imparare da quell'errore.Secondo, l'offendere il codice è il risultato di un 'get it done quick' mentalità e inadeguata progettazione/sperimentazione ha portato a questo problema non si presenta per prima.Avevo generato più considerevole test set di dati da utilizzare come test di ingresso per questa nuova funzionalità, mi piacerebbe non hanno sprecato il mio tempo né la tua.Il mio test su DB da un lato manca la profondità che la mia applicazione lato ha posto in essere.Ora ho avuto l'opportunità di migliorare.

Riferimento:SPIEGARE Istruzione

È stato utile?

Soluzione

L'eliminazione di dati da InnoDB è l'operazione più costosa che puoi richiederne. Come hai già scoperto, la query in sé non è il problema: la maggior parte di essi verrà comunque ottimizzata per lo stesso piano di esecuzione.

Mentre può essere difficile capire perché le DELETE di tutti i casi siano le più lente, c'è una spiegazione piuttosto semplice. InnoDB è un motore di archiviazione transazionale. Ciò significa che se la tua query venisse interrotta a metà, tutti i record rimarrebbero comunque in atto come se nulla fosse successo. Una volta completato, tutto andrà nello stesso istante. Durante l'eliminazione, gli altri client che si connettono al server vedranno i record fino al completamento dell'eliminazione.

Per raggiungere questo obiettivo, InnoDB utilizza una tecnica chiamata MVCC (Multi Version Concurrency Control). Ciò che fondamentalmente fa è dare ad ogni connessione una vista istantanea dell'intero database com'era quando è iniziata la prima dichiarazione della transazione. Per raggiungere questo obiettivo, ogni record in InnoDB internamente può avere più valori, uno per ogni istantanea. Questo è anche il motivo per cui COUNTing su InnoDB richiede del tempo - dipende dallo stato dell'istantanea che vedi in quel momento.

Per la tua transazione DELETE, ogni record identificato in base alle condizioni della tua query, viene contrassegnato per l'eliminazione. Poiché altri client potrebbero accedere ai dati contemporaneamente, non è possibile rimuoverli immediatamente dalla tabella, poiché devono vedere le rispettive istantanee per garantire l'atomicità della cancellazione.

Dopo che tutti i record sono stati contrassegnati per l'eliminazione, la transazione viene eseguita correttamente. E anche in questo caso non possono essere immediatamente rimossi dalle pagine dei dati effettivi, prima che anche tutte le altre transazioni che hanno funzionato con un valore di snapshot prima della transazione DELETE siano terminate.

Quindi, in effetti, i tuoi 3 minuti non sono poi così lenti, considerando il fatto che tutti i record devono essere modificati per prepararli alla rimozione in modo sicuro per le transazioni. Probabilmente & Quot; sentirai & Quot; il disco rigido funziona mentre viene eseguita l'istruzione. Ciò è causato dall'accesso a tutte le righe. Per migliorare le prestazioni, puoi provare ad aumentare le dimensioni del pool di buffer InnoDB per il tuo server e provare a limitare altri accessi al database mentre ELIMINA, riducendo così anche il numero di versioni storiche che InnoDB deve conservare per record. Con la memoria aggiuntiva InnoDB potrebbe essere in grado di leggere il tuo tavolo (principalmente) in memoria ed evitare un po 'di tempo di ricerca del disco.

Altri suggerimenti

Il tuo tempo di tre minuti sembra molto lento. La mia ipotesi è che la colonna ID non venga indicizzata correttamente. Se tu potessi fornire l'esatta definizione della tabella che stai usando sarebbe utile.

Ho creato un semplice script Python per produrre dati di test ed eseguito diverse versioni della query di eliminazione sullo stesso set di dati. Ecco le definizioni della mia tabella:

drop table if exists a;
create table a
 (id bigint unsigned  not null primary key,
  data varchar(255) not null) engine=InnoDB;

drop table if exists b;
create table b like a;

Ho quindi inserito 100k righe in a e 25k righe in b (di cui 22.5k erano anche in a). Ecco i risultati dei vari comandi di eliminazione. A proposito, ho lasciato cadere e ripopolato il tavolo tra le esecuzioni.

mysql> DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE a.id=b.id);
Query OK, 22500 rows affected (1.14 sec)

mysql> DELETE FROM a USING a LEFT JOIN b ON a.id=b.id WHERE b.id IS NOT NULL;
Query OK, 22500 rows affected (0.81 sec)

mysql> DELETE a FROM a INNER JOIN b on a.id=b.id;
Query OK, 22500 rows affected (0.97 sec)

mysql> DELETE QUICK a.* FROM a,b WHERE a.id=b.id;
Query OK, 22500 rows affected (0.81 sec)

Tutti i test sono stati eseguiti su un Intel Core2 quad-core da 2,5 GHz, 2 GB di RAM con Ubuntu 8.10 e MySQL 5.0. Si noti che l'esecuzione di un'istruzione sql è ancora a thread singolo.


Aggiornamento:

Ho aggiornato i miei test per utilizzare lo schema di itsmatt. L'ho leggermente modificato rimuovendo l'incremento automatico (sto generando dati sintetici) e la codifica del set di caratteri (non funzionava - non ho scavato).

Ecco le mie nuove definizioni di tabella:

drop table if exists a;
drop table if exists b;
drop table if exists c;

create table c (id varchar(30) not null primary key) engine=InnoDB;

create table a (
  id bigint(20) unsigned not null primary key,
  c_id varchar(30) not null,
  h int(10) unsigned default null,
  i longtext,
  j bigint(20) not null,
  k bigint(20) default null,
  l varchar(45) not null,
  m int(10) unsigned default null,
  n varchar(20) default null,
  o bigint(20) not null,
  p tinyint(1) not null,
  key l_idx (l),
  key h_idx (h),
  key m_idx (m),
  key c_id_idx (id, c_id),
  key c_id_fk (c_id),
  constraint c_id_fk foreign key (c_id) references c(id)
) engine=InnoDB row_format=dynamic;

create table b like a;

Quindi eseguo nuovamente gli stessi test con 100k righe in a e 25k righe in b (e ripopolamento tra le esecuzioni).

mysql> DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE a.id=b.id);
Query OK, 22500 rows affected (11.90 sec)

mysql> DELETE FROM a USING a LEFT JOIN b ON a.id=b.id WHERE b.id IS NOT NULL;
Query OK, 22500 rows affected (11.48 sec)

mysql> DELETE a FROM a INNER JOIN b on a.id=b.id;
Query OK, 22500 rows affected (12.21 sec)

mysql> DELETE QUICK a.* FROM a,b WHERE a.id=b.id;
Query OK, 22500 rows affected (12.33 sec)

Come puoi vedere, è un po 'più lento di prima, probabilmente a causa dei molteplici indici. Tuttavia, non è affatto vicino al segno dei tre minuti.

Qualcos'altro che potresti voler guardare è spostare il campo longtext alla fine dello schema. Mi sembra di ricordare che mySQL funziona meglio se tutti i campi con dimensioni limitate sono primi e testo, BLOB, ecc. Sono alla fine.

Prova questo:

DELETE a
FROM a
INNER JOIN b
 on a.id = b.id

L'uso delle sottoquery tende ad essere più lento dei join poiché vengono eseguiti per ciascun record nella query esterna.

Questo è quello che faccio sempre, quando devo operare con dati di grandi dimensioni (qui: una tabella di prova di esempio con 150000 righe):

drop table if exists employees_bak;
create table employees_bak like employees;
insert into employees_bak 
    select * from employees
    where emp_no > 100000;

rename table employees to employees_todelete;
rename table employees_bak to employees;

In questo caso sql filtra 50000 righe nella tabella di backup. La query cascade si esegue sulla mia macchina lenta in 5 secondi. È possibile sostituire l'inserto in select con la propria query di filtro.

Questo è il trucco per eseguire la cancellazione di massa su grandi database!; =)

Stai eseguendo la tua subquery su 'b' per ogni riga in 'a'.

Prova:

DELETE FROM a USING a LEFT JOIN b ON a.id = b.id WHERE b.id IS NOT NULL;

Prova questo:

DELETE QUICK A.* FROM A,B WHERE A.ID=B.ID

È molto più veloce delle normali query.

Fare riferimento alla sintassi: http://dev.mysql.com /doc/refman/5.0/en/delete.html

So che questa domanda è stata praticamente risolta a causa delle omissioni di indicizzazione di OP, ma vorrei offrire questo consiglio aggiuntivo, che è valido per un caso più generico di questo problema.

Ho affrontato personalmente la necessità di eliminare molte righe da una tabella che esiste in un'altra e nella mia esperienza è meglio fare quanto segue, soprattutto se si prevede che vengano eliminate molte righe. Questa tecnica, soprattutto, migliorerà il ritardo dello slave di replica, poiché più a lungo viene eseguita ogni singola query mutatore, peggiore sarà il ritardo (la replica è a thread singolo).

Quindi, eccolo qui: esegui prima un SELECT, come query separata , ricordando gli ID restituiti nel tuo script / applicazione, quindi continua a cancellare in batch (diciamo, 50.000 righe alla volta ). Ciò consentirà di ottenere quanto segue:

  • ognuna delle istruzioni di eliminazione non bloccherà la tabella per troppo tempo, evitando così che il ritardo di replica sfugga al controllo . È particolarmente importante se si fa affidamento sulla propria replica per fornire dati relativamente aggiornati. Il vantaggio dell'utilizzo dei batch è che se si rileva che ogni query DELETE richiede ancora troppo tempo, è possibile regolarla in modo che sia più piccola senza toccare alcuna struttura DB.
  • un altro vantaggio dell'utilizzo di un SELECT separato è che lo stesso SELECT potrebbe impiegare molto tempo per essere eseguito , specialmente se per qualsiasi motivo non può utilizzare i migliori indici DB. Se SELEZIONA è interno a un ELIMINA, quando l'intera istruzione migra verso gli slave, dovrà eseguire nuovamente SELEZIONA, potenzialmente ritardando gli schiavi perché deve ripetere nuovamente la selezione lunga. Lo slave lag, di nuovo, soffre molto. Se si utilizza una query SELECT separata, questo problema scompare, poiché tutto ciò che si passa è un elenco di ID.

Fammi sapere se c'è un errore nella mia logica da qualche parte.

Per ulteriori discussioni sul ritardo di replica e sui modi per combatterlo, simile a questo, vedere MySQL Slave Lag (Delay) spiegato e 7 modi per combatterlo

P.S. Una cosa a cui prestare attenzione è, ovviamente, le potenziali modifiche alla tabella tra il momento in cui SELECT termina e DELETE iniziano. Ti lascerò gestire tali dettagli utilizzando le transazioni e / o la logica pertinenti alla tua applicazione.

DELETE FROM a WHERE id IN (SELECT id FROM b)

Forse dovresti ricostruire le indicazioni prima di eseguire una query così hugh. Bene, dovresti ricostruirli periodicamente.

REPAIR TABLE a QUICK;
REPAIR TABLE b QUICK;

, quindi esegui una delle query precedenti (ad es.)

DELETE FROM a WHERE id IN (SELECT id FROM b)

La query stessa è già in una forma ottimale, l'aggiornamento degli indici fa sì che l'intera operazione impieghi così tanto tempo. Puoi disabilitare le chiavi su quella tabella prima del operazione, che dovrebbe accelerare le cose. Puoi riattivarli in un secondo momento, se non ti servono immediatamente.

Un altro approccio sarebbe l'aggiunta di una deleted flag-column alla tabella e la regolazione di altre query in modo che tengano conto di quel valore. Il tipo booleano più veloce in mysql è CHAR(0) NULL (true = '', false = NULL). Sarebbe un'operazione veloce, puoi eliminare i valori in seguito.

Gli stessi pensieri espressi nelle dichiarazioni sql:

ALTER TABLE a ADD COLUMN deleted CHAR(0) NULL DEFAULT NULL;

-- The following query should be faster than the delete statement:
UPDATE a INNER JOIN b SET a.deleted = '';

-- This is the catch, you need to alter the rest
-- of your queries to take the new column into account:
SELECT * FROM a WHERE deleted IS NULL;

-- You can then issue the following queries in a cronjob
-- to clean up the tables:
DELETE FROM a WHERE deleted IS NOT NULL;

Se anche quello non è quello che vuoi, puoi dare un'occhiata a ciò che i documenti mysql dicono sul velocità delle istruzioni di eliminazione .

A proposito, dopo aver pubblicato quanto sopra sul mio blog, Baron Schwartz di Percona ha portato alla mia attenzione che il suo maatkit ha già uno strumento proprio per questo scopo: mk-archiver. http://www.maatkit.org/doc/mk-archiver.html.

È molto probabilmente il tuo miglior strumento per il lavoro.

Ovviamente la query SELECT che crea le basi della tua operazione DELETE è abbastanza veloce, quindi penso che il vincolo della chiave esterna o gli indici siano i motivi della tua query estremamente lenta.

Prova

SET foreign_key_checks = 0;
/* ... your query ... */
SET foreign_key_checks = 1;

Questo disabiliterebbe i controlli sulla chiave esterna. Purtroppo non è possibile disabilitare (almeno non so come) gli aggiornamenti delle chiavi con una tabella InnoDB. Con una tabella MyISAM potresti fare qualcosa del genere

ALTER TABLE a DISABLE KEYS
/* ... your query ... */
ALTER TABLE a ENABLE KEYS 

In realtà non ho testato se queste impostazioni avrebbero influenzato la durata della query. Ma vale la pena provare.

Collegare il database utilizzando il terminale ed eseguire il comando riportato di seguito, guardate il risultato ciascuno di essi, troverete che i tempi di eliminare 10, 100, 1000, 10000, 100000 record non sono Moltiplicate.

  DELETE FROM #{$table_name} WHERE id < 10;
  DELETE FROM #{$table_name} WHERE id < 100;
  DELETE FROM #{$table_name} WHERE id < 1000;
  DELETE FROM #{$table_name} WHERE id < 10000;
  DELETE FROM #{$table_name} WHERE id < 100000;

Il tempo di eliminazione di 10 mila record non è 10 volte tanto quanto l'eliminazione di 100 migliaia di record.Quindi, tranne che per trovare un modo per eliminare i record più veloce, ci sono alcuni metodi indiretti.

1, Siamo in grado di rinominare il table_name per table_name_bak, e quindi selezionare i record da table_name_bak a table_name.

2, Per eliminare 10000 record, siamo in grado di eliminare 1000 record di 10 volte.C'è un esempio di script ruby per farlo.

#!/usr/bin/env ruby
require 'mysql2'


$client = Mysql2::Client.new(
  :as => :array,
  :host => '10.0.0.250',
  :username => 'mysql',
  :password => '123456',
  :database => 'test'
)


$ids = (1..1000000).to_a
$table_name = "test"

until $ids.empty?
  ids = $ids.shift(1000).join(", ")
  puts "delete =================="
  $client.query("
                DELETE FROM #{$table_name}
                WHERE id IN ( #{ids} )
                ")
end

La tecnica di base per eliminare più righe da MySQL in una singola tabella tramite il campo ID

DELETE FROM tbl_name WHERE id <= 100 AND id >=200; Questa query è responsabile dell'eliminazione della condizione corrispondente tra 100 E 200 dalla determinata tabella

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