2015-07-28 14 views
15

Ich habe die folgende Abfrage. Die Idee ist, dass es mir erlaubt zu wissen, was groups und anschließend users Zugang zu jedem component_instance haben. Ich frage mich, ob es ein besserer Weg, dies als die Abfrage zu tun ist ziemlich langsam, aber es ist wirklich praktisch, diese zusätzliche Spalten jedes Mal, wenn ich mit dieser Tabelle tun hat:Eine GROUP_CONCAT Abfrage effizienter machen

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 

Die Berechtigungstabelle wie so ist (entschuldigen Sie die 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 

die Arten von Berechtigungen sind edit und view. Eine Gruppe kann entweder einem oder beiden zugewiesen werden. Berechtigungen sind auch rekursiv, wenn wir keine Gruppenberechtigungen für eine component_instance haben, müssen wir ihre Vorfahren überprüfen, um die ersten zu finden, bei denen Berechtigungen gesetzt sind (falls vorhanden). Dies macht es sehr wichtig, die eine Abfrage zu haben, da ich diese Abfrage dann mit der Auswahllogik kombinieren kann, die der Edelstein ancestry bereitstellt (materialisierter Pfadbaum).

aktualisieren

Ich habe schneller, da diese Abfrage Benchmarks gefunden:

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 

Hier ist ein für die Abfrage über EXPLAIN ist und die Tabelle Anweisungen 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 

Die ORDER BY kommt von der ancestry gem aber wenn es eine bessere Möglichkeit gibt, dies zu tun, würde ich glücklich sein, submi t das als Pull-Anfrage an sie.

+0

Es ist üblich, all Ihren Text in der Frage zu halten, wenn ich Sie wäre, würde ich jedes Update mit einer 'UPDATE'-Zeile trennen und den Stamm im Frageteil behalten. Es macht es viel klarer zu lesen. – Mehran

+0

Danke Mehran, ich habe das aktualisiert. Ich ging zuerst meine eigene Frage zu beantworten, dann dachte ich, eine Prämie zu tun. –

+0

Auch ich denke, wenn Sie die zweite Version verwenden, können Sie die letzten zwei Joins weglassen und verwenden Sie view_groups_users.user_id und edit_groups_users.user_id in der group_concat – maraca

Antwort

1

NULL wird zuerst platziert (könnte COALESCE verwenden, um NULL durch etwas anderes zu ersetzen, anstatt eine zusätzliche Sortierspalte zu verwenden). Die zweite Sache ist das Reduzieren der Joins, da die letzten beiden auf der ID waren, auf der wir uns befinden.

+0

Danke Maraca, Entschuldigung, ich habe die anderen Benutzer zuerst angenommen, weil ich dachte, dass du es warst! Das habe ich umgekehrt. Du hast recht, NULL's sind die Ersten. Ich vermute, dass ein bisschen Code einen anderen Datenbanktyp unterstützt, vielleicht weil die Ahnenbibliothek nicht nur für MySQL ist. Ich kann diesen Teil jedoch überschreiben, also werde ich das tun. –

+0

Leider führt Ihre obige Abfrage zu unterschiedlichen Ergebnissen für view_user_ids und edit_user_ids im Vergleich zu meiner Joins-Abfrage. Beide laufen ungefähr zur gleichen Zeit ab. Wenn Sie also nicht herausfinden wollen, warum das der Fall ist, würde ich die einfachere Antwort ohne die Unterauswahl akzeptieren. –

+0

Ich denke, sie müssen sein. Wenn man die Ergebnisse betrachtet, sieht es so aus, als ob der Subselect nur die erste Gruppen-ID ergreift und den Rest ignoriert. Die Extra-Joins funktionieren definitiv wie erwartet. –

2

Es ist fast unmöglich, Ihre Abfrage zu optimieren, wenn wir Ihre Tabellenstruktur und Indizes nicht haben. Die Verwendung einer EXPLAIN-Anweisung ist der notwendige Teil von Abfrageoptimierungen.

Ohne die genannten Informationen, alles, was ich zu Ihrer Frage kommentieren kann, ist, dass Ihr ORDER BY Teil von einigen Optimierung für sicher profitieren kann. Die Verwendung von Funktionen oder Anweisungen in einer Bedingung führt immer zu einem Desaster. Auch die Verwendung eines nullbaren Feldes in einem ORDER BY wird zu Problemen führen. Vielleicht wäre der einfachste Weg, ein neues Feld zu Ihrer Tabelle hinzuzufügen, die 0s und 1s anstelle der aktuellen CASE-Anweisung enthält.

Vergessen Sie nicht, dass ein Index für jedes Feld innerhalb einer Bedingung/Reihenfolge von/group by immer erforderlich ist, wenn die Anzahl der Datensätze beträchtlich ist.

[UPDATE]

Ihre Anfrage ist ziemlich einfach. Die EXPLAIN ‚s Ergebnis zeigt, dass die einzigen Teile, geeignet als Kandidat sind indiziert werden:

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

Die EXPLAIN‘ s zweite Zeile zeigt, dass es permissions in der Abfrage verwendet kein Index der Tabelle ist. Das liegt daran, dass MySQL einige Regeln für die Verwendung von Indizes hat:

  • In jeder einzelnen (Teil-) Abfrage kann nur ein Index jeder Tabelle verwendet werden.
  • Jeder Index kann nur verwendet werden, wenn alle seine Felder in der Abfrage erwähnt werden (wie in Bedingungen/Reihenfolge von/Gruppe von).
  • In Anbetracht Ihrer Abfrage und der Tatsache, dass alle vier Felder der Tabelle permissions erwähnt werden, benötigen Sie einen Index für alle vier von ihnen oder es ist nutzlos.

    Die ORDER BY kann jedoch von der oben erwähnten Änderung profitieren.

    +0

    Danke Mehran, ich habe in den zusätzlichen Details hinzugefügt, die Sie angefordert haben. Ich bin definitiv an der ORDER BY-Anweisung interessiert, siehe die aktualisierte Frage. Ich habe die Abfrage in meiner Antwort oben und nicht in der Frage erläutert. –