2015-07-28 12 views
15

Ho la seguente query. L'idea è che mi permette di sapere che cosa groups, e successivamente users, avere accesso a ogni component_instance. Mi chiedo se c'è un modo migliore per fare questo, come la query è piuttosto lento, ma è davvero comodo avere queste colonne in più ogni volta che ho a che fare con questo tavolo:Rendere più efficiente una query GROUP_CONCAT

SELECT component_instances.*, 
GROUP_CONCAT(DISTINCT IF(permissions.view, groups.id, NULL)) AS view_group_ids, 
GROUP_CONCAT(DISTINCT IF(permissions.edit, groups.id, NULL)) AS edit_group_ids, 
GROUP_CONCAT(DISTINCT IF(permissions.view, users.id, NULL)) AS view_user_ids, 
GROUP_CONCAT(DISTINCT IF(permissions.edit, users.id, NULL)) AS edit_user_ids 
FROM `component_instances` 
LEFT OUTER JOIN permissions ON permissions.component_instance_id = component_instances.id 
LEFT OUTER JOIN groups ON groups.id = permissions.group_id 
LEFT OUTER JOIN groups_users ON groups_users.group_id = groups.id 
LEFT OUTER JOIN users ON users.id = groups_users.user_id 
GROUP BY component_instances.id 
ORDER BY (case when component_instances.ancestry is null then 0 else 1 end), component_instances.ancestry, position 

La tabella delle autorizzazioni è in questo modo (scusate il Rails!):

create_table "permissions", :force => true do |t| 
    t.integer "component_instance_id" 
    t.integer "group_id" 
    t.boolean "view",     :default => false 
    t.boolean "edit",     :default => false 
end 

i tipi di permessi sono edit e view. A un gruppo può essere assegnato uno o entrambi. Le autorizzazioni sono anche ricorsive in quanto se non ci sono permessi di gruppo su un component_instance, dovremmo controllare i suoi antenati per trovare il primo dove sono impostate le autorizzazioni (se presenti). Questo rende la query una cosa abbastanza importante perché posso combinare questa query con la logica di selezione fornita dalla gemma ancestry (albero del percorso materializzato).

Aggiornamento

allora ho trovato questa interrogazione benchmark più veloce:

SELECT component_instances.*, 
GROUP_CONCAT(DISTINCT view_groups.id) AS view_group_ids, 
GROUP_CONCAT(DISTINCT edit_groups.id) AS edit_group_ids, 
GROUP_CONCAT(DISTINCT view_users.id) AS view_user_ids, 
GROUP_CONCAT(DISTINCT edit_users.id) AS edit_user_ids 
FROM `component_instances` 
LEFT OUTER JOIN permissions ON permissions.component_instance_id = component_instances.id 
LEFT OUTER JOIN groups view_groups ON view_groups.id = permissions.group_id AND permissions.view = 1 
LEFT OUTER JOIN groups edit_groups ON edit_groups.id = permissions.group_id AND permissions.edit = 1 
LEFT OUTER JOIN groups_users view_groups_users ON view_groups_users.group_id = view_groups.id 
LEFT OUTER JOIN groups_users edit_groups_users ON edit_groups_users.group_id = edit_groups.id 
LEFT OUTER JOIN users view_users ON view_users.id = view_groups_users.user_id 
LEFT OUTER JOIN users edit_users ON edit_users.id = edit_groups_users.user_id 
GROUP BY component_instances.id 
ORDER BY (case when component_instances.ancestry is null then 0 else 1 end), component_instances.ancestry, position 

Ecco un EXPLAIN per la query di cui sopra e la tabella CREATE:

+----+-------------+---------------------+--------+-----------------------------------------------+--------------------------------------------+---------+--------------------------------------------+------+------------------------------------------------------+ 
| id | select_type | table    | type | possible_keys         | key          | key_len | ref          | rows | Extra            | 
+----+-------------+---------------------+--------+-----------------------------------------------+--------------------------------------------+---------+--------------------------------------------+------+------------------------------------------------------+ 
| 1 | SIMPLE  | component_instances | ALL | PRIMARY,index_component_instances_on_ancestry | NULL          | NULL | NULL          | 119 | "Using temporary; Using filesort"     | 
| 1 | SIMPLE  | permissions   | ALL | NULL           | NULL          | NULL | NULL          | 6 | "Using where; Using join buffer (Block Nested Loop)" | 
| 1 | SIMPLE  | view_groups   | eq_ref | PRIMARY          | PRIMARY         | 4  | 05707d890df9347c.permissions.group_id  | 1 | "Using where; Using index"       | 
| 1 | SIMPLE  | edit_groups   | eq_ref | PRIMARY          | PRIMARY         | 4  | 05707d890df9347c.permissions.group_id  | 1 | "Using where; Using index"       | 
| 1 | SIMPLE  | view_groups_users | ref | index_groups_users_on_group_id_and_user_id | index_groups_users_on_group_id_and_user_id | 5  | 05707d890df9347c.view_groups.id   | 1 | "Using index"          | 
| 1 | SIMPLE  | edit_groups_users | ref | index_groups_users_on_group_id_and_user_id | index_groups_users_on_group_id_and_user_id | 5  | 05707d890df9347c.edit_groups.id   | 1 | "Using index"          | 
| 1 | SIMPLE  | view_users   | eq_ref | PRIMARY          | PRIMARY         | 4  | 05707d890df9347c.view_groups_users.user_id | 1 | "Using index"          | 
| 1 | SIMPLE  | edit_users   | eq_ref | PRIMARY          | PRIMARY         | 4  | 05707d890df9347c.edit_groups_users.user_id | 1 | "Using index"          | 
+----+-------------+---------------------+--------+-----------------------------------------------+--------------------------------------------+---------+--------------------------------------------+------+------------------------------------------------------+ 

CREATE TABLE `component_instances` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `visible` int(11) DEFAULT '1', 
    `instance_id` int(11) DEFAULT NULL, 
    `deleted_on` date DEFAULT NULL, 
    `instance_type` varchar(255) DEFAULT NULL, 
    `component_id` int(11) DEFAULT NULL, 
    `deleted_root_item` int(11) DEFAULT NULL, 
    `locked_until` datetime DEFAULT NULL, 
    `theme_id` int(11) DEFAULT NULL, 
    `position` int(11) DEFAULT NULL, 
    `ancestry` varchar(255) DEFAULT NULL, 
    `ancestry_depth` int(11) DEFAULT '0', 
    `cached_name` varchar(255) DEFAULT NULL, 
    PRIMARY KEY (`id`), 
    KEY `index_component_instances_on_ancestry` (`ancestry`) 
) ENGINE=InnoDB AUTO_INCREMENT=121 DEFAULT CHARSET=utf8 

CREATE TABLE `groups` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `name` varchar(255) NOT NULL DEFAULT '', 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 

CREATE TABLE `groups_users` (
    `group_id` int(11) DEFAULT NULL, 
    `user_id` int(11) DEFAULT NULL, 
    KEY `index_groups_users_on_group_id_and_user_id` (`group_id`,`user_id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 

CREATE TABLE `permissions` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `component_instance_id` int(11) DEFAULT NULL, 
    `group_id` int(11) DEFAULT NULL, 
    `view` tinyint(1) DEFAULT '0', 
    `edit` tinyint(1) DEFAULT '0', 
    PRIMARY KEY (`id`), 
    KEY `edit_permissions_index` (`edit`,`group_id`,`component_instance_id`), 
    KEY `view_permissions_index` (`view`,`group_id`,`component_instance_id`) 
) ENGINE=InnoDB AUTO_INCREMENT=28 DEFAULT CHARSET=utf8 

CREATE TABLE `users` (
    `id` int(11) NOT NULL AUTO_INCREMENT, 
    `real_name` varchar(255) DEFAULT NULL, 
    `username` varchar(255) NOT NULL DEFAULT '', 
    `email` varchar(255) NOT NULL DEFAULT '', 
    `crypted_password` varchar(255) DEFAULT NULL, 
    `administrator` int(11) NOT NULL DEFAULT '0', 
    `password_salt` varchar(255) DEFAULT NULL, 
    `remember_token_expires` datetime DEFAULT NULL, 
    `persistence_token` varchar(255) DEFAULT NULL, 
    `disabled` tinyint(1) DEFAULT NULL, 
    `time_zone` varchar(255) DEFAULT NULL, 
    `login_count` int(11) DEFAULT NULL, 
    `failed_login_count` int(11) DEFAULT NULL, 
    `last_request_at` datetime DEFAULT NULL, 
    `current_login_at` datetime DEFAULT NULL, 
    `last_login_at` datetime DEFAULT NULL, 
    `current_login_ip` varchar(255) DEFAULT NULL, 
    `last_login_ip` varchar(255) DEFAULT NULL, 
    `perishable_token` varchar(255) NOT NULL DEFAULT '', 
    PRIMARY KEY (`id`), 
    UNIQUE KEY `index_users_on_username` (`username`), 
    KEY `index_users_on_perishable_token` (`perishable_token`) 
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8 

Il ORDER BY proviene dal ancestry gem ma se c'è un modo migliore per farlo sarei felice di submi che come una richiesta di pull a loro.

+0

E 'consuetudine per mantenere tutto il vostro testo nella domanda, se fossi in te avrei separare la mia ogni aggiornamento utilizzando una linea 'UPDATE' e mantenere lo stelo tutti nella parte domanda. Lo rende molto più chiaro da leggere. – Mehran

+0

Grazie a Mehran, l'ho aggiornato. Inizialmente sono andato a rispondere alla mia domanda e poi ho pensato di fare un premio. –

+0

Inoltre, penso che se si utilizza la seconda versione è possibile omettere gli ultimi due join e utilizzare view_groups_users.user_id e edit_groups_users.user_id nel gruppo_concat – maraca

risposta

1

NULL viene inserito per primo (potrebbe utilizzare COALESCE per sostituire NULL anche con qualcos'altro invece di utilizzare una colonna di ordinamento aggiuntiva). La seconda cosa è ridurre i join, perché gli ultimi due erano nell'ID su cui ci siamo concatenati.

SELECT 
    component_instances.*, 
    GROUP_CONCAT(DISTINCT view_groups.id) AS view_group_ids, 
    GROUP_CONCAT(DISTINCT edit_groups.id) AS edit_group_ids, 
    GROUP_CONCAT(DISTINCT view_groups_users.user_id) AS view_user_ids, 
    GROUP_CONCAT(DISTINCT edit_groups_users.user_id) AS edit_user_ids 
FROM 
    `component_instances` 
    LEFT OUTER JOIN permissions 
     ON permissions.component_instance_id = component_instances.id 
    LEFT OUTER JOIN groups view_groups 
     ON view_groups.id = permissions.group_id AND permissions.view = 1 
    LEFT OUTER JOIN groups edit_groups 
     ON edit_groups.id = permissions.group_id AND permissions.edit = 1 
    LEFT OUTER JOIN groups_users view_groups_users 
     ON view_groups_users.group_id = view_groups.id 
    LEFT OUTER JOIN groups_users edit_groups_users 
     ON edit_groups_users.group_id = edit_groups.id 
GROUP BY 
    component_instances.id 
ORDER BY 
    component_instances.ancestry, -- MySQL was sorting the NULL values already correctly 
    position 
; 
+0

Grazie Maraca, scusa ho accettato gli altri utenti rispondono per primi perché pensavo fossi tu! L'ho invertito. Hai ragione, i NULL sono posti per primi. Ho il sospetto che il codice sia in grado di supportare un altro tipo di database, forse perché la libreria di ascendenza non è solo per MySQL. Posso scavalcare quella parte però, quindi lo farò. –

+0

Sfortunatamente la tua query sopra genera risultati diversi per view_user_ids e edit_user_ids rispetto alla mia query joins. Entrambe eseguono più o meno nello stesso tempo, quindi a meno che non vogliate capire perché possa essere, sarei felice di accettare la risposta più semplice senza i sottoseleziona. –

+0

Penso che debbano essere. Guardando i risultati, è come se la sottoselezione stia solo afferrando il primo id del gruppo e ignorando il resto. I join extra funzionano sicuramente come previsto. –

2

È quasi impossibile ottimizzare la query se non abbiamo la struttura e gli indici della tabella. L'utilizzo di un'istruzione EXPLAIN è la parte necessaria delle ottimizzazioni della query.

Senza le informazioni menzionate, tutto quello che posso commentare sulla tua domanda è che la tua parte ORDER BY può sicuramente beneficiare di un'ottimizzazione. L'utilizzo di qualsiasi funzione o istruzione in una condizione causerà sempre un disastro. Anche l'utilizzo di un campo Null in uno ORDER BY causerà problemi. Forse il modo più semplice sarebbe aggiungere un nuovo campo alla tua tabella tenendo gli 0 e gli 1 invece dell'attuale dichiarazione CASE.

Non dimenticare che avere indice su qualsiasi campo all'interno di una condizione/ordine per/gruppo è sempre necessario se il numero di record è considerevole.

[UPDATE]

tua domanda è piuttosto semplice. Il EXPLAIN 'risultato s mostra che le sole parti adatto come candidato per essere indicizzato sono:

CREATE INDEX inx4 ON permissions (`component_instance_id`, `group_id`, `edit`, `view`); 

il EXPLAIN' s seconda riga indica che non c'è alcun indice di tabella permissions utilizzato nella query. Questo perché MySQL ha un paio di regole quando userà indici:

  • In ogni query (sotto) può essere utilizzato un solo indice di ogni tabella.
  • Qualsiasi indice può essere utilizzato solo se tutti i relativi campi sono menzionati nella query (come in condizioni/ordine per/gruppo di).

Considerando la tua ricerca, e il fatto che tutti e quattro i campi della tabella permissions sono menzionati, avrete bisogno di un indice su tutti e quattro di loro, o è inutile.

Eppure il ORDER BY può beneficiare dell'emendamento che ho citato prima.

+0

Grazie a Mehran, ho aggiunto i dettagli extra richiesti. Sono decisamente interessato all'istruzione ORDER BY, vedere la domanda aggiornata. Ho spiegato la query nella mia risposta sopra piuttosto che quella nella domanda. –