Dai log del database rileviamo alcune query con un ORDER BY non necessario in una subquery generata da user.rb#L379. Una di esse è la seguente:
SELECT
"group_users"."group_id"
FROM
"group_users"
WHERE
"group_users"."group_id" IN (
SELECT
"groups"."id"
FROM
"groups"
WHERE
(groups.id > 0)
ORDER BY
name ASC
)
AND "group_users"."user_id" = 762
L’uso di ORDER BY nella subquery è privo di significato e può essere rimosso per accelerare la query. Secondo i nostri test, ciò può migliorare le prestazioni della query del 18%-90% (a seconda del tempo speso nell’ordinamento nella subquery).
Eseguiamo gli spec in Discourse e dal database rileviamo una query lenta (6480259 nanosecondi, rispetto a una query simile) con un DISTINCT non necessario generato da site_settings_controller.rb#L141, come segue:
SELECT
DISTINCT users.id
FROM
"users" CROSS
JOIN tags t
LEFT JOIN tag_users tu ON users.id = tu.user_id
AND t.id = tu.tag_id
WHERE
(
t.id IN (1825)
AND tu.notification_level IS NULL
)
Quando tags.id e notification_level hanno valori specificati, grazie all’indice UNIQUE (tag_id, user_id, notification_level) su tag_users e alla PRIMARY KEY(id) su tags, né il CROSS JOIN né il LEFT JOIN generano record duplicati. Ciò significa che possiamo rimuovere DISTINCT per accelerare la query.
Questa query ottimizzata impiega 4538891 nanosecondi (miglioramento del 30%).
Dal Database Log rileviamo una query lenta (6064379 nanosecondi) con molte subquery e union generate da group.rb#L112, come segue:
SELECT
"groups"."id",
"groups"."name"
FROM
"groups"
INNER JOIN "group_users" ON "groups"."id" = "group_users"."group_id"
WHERE
"group_users"."user_id" = 296
AND (groups.id > 0)
AND (
groups.id IN (
SELECT
id
FROM
groups
WHERE
visibility_level = 0
UNION ALL
SELECT
id
FROM
groups
WHERE
visibility_level = 1
AND 296 IS NOT NULL
UNION ALL
SELECT
g.id
FROM
groups g
JOIN group_users gu ON gu.group_id = g.id
AND gu.user_id = 296
WHERE
g.visibility_level = 2
UNION ALL
SELECT
g.id
FROM
groups g
LEFT JOIN group_users gu ON gu.group_id = g.id
AND gu.user_id = 296
AND gu.owner
WHERE
g.visibility_level = 3
AND (
gu.id IS NOT NULL
OR FALSE
)
UNION ALL
SELECT
g.id
FROM
groups g
JOIN group_users gu ON gu.group_id = g.id
AND gu.user_id = 296
AND gu.owner
WHERE
g.visibility_level = 4
)
)
ORDER BY
name ASC
Ovviamente è equivalente alla query riportata di seguito:
SELECT
"groups"."id",
"groups"."name"
FROM
"groups"
INNER JOIN "group_users" ON "groups"."id" = "group_users"."group_id"
WHERE
"group_users"."user_id" = 296 AND
groups.visibility_level IN (0, 1, 2, 3, 4)
ORDER BY
name ASC
Questa query ottimizzata impiega 378062 nanosecondi (miglioramento del 93%).
Mi scuso, la query riscritta corretta dovrebbe essere
SELECT
"groups"."id",
"groups"."name"
FROM
"groups"
INNER JOIN "group_users" gu ON "groups"."id" = gu."group_id"
WHERE
"group_users"."user_id" = 296
AND (groups.id > 0)
AND (
groups.visibility_level IN (0, 1, 2)
OR (groups.visibility_level IN (3,4) and gu.owner)
)
ORDER BY
name ASC
La sottoquery con il predicato visibility_level = 0,1,2 può essere sostituita da groups.visibility_level IN (0, 1, 2), e la sottoquery con il predicato visibility_level = 3,4 può essere sostituita da OR (groups.visibility_level IN (3,4) and gu.owner).
Dal database rileviamo una query lenta (1141257 nanosecondi) con JOIN e DISTINCT non necessari, generata da directory_items_controller.rb, come segue:
SELECT
COUNT(DISTINCT "directory_items"."id")
FROM
"directory_items"
LEFT OUTER JOIN "users" ON "users"."id" = "directory_items"."user_id"
LEFT OUTER JOIN "group_users" ON "group_users"."user_id" = "users"."id"
LEFT OUTER JOIN "groups" ON "groups"."id" = "group_users"."group_id"
LEFT OUTER JOIN "user_stats" ON "user_stats"."user_id" = "directory_items"."user_id"
WHERE
"directory_items"."period_type" = 1
AND "groups"."id" = 2898
Non ha recuperato dati da user_stats e groups.id = 2898 può essere sostituito da group_users.group_id = 2898, il che significa che possiamo rimuovere le tabelle groups e user_stats dalla query. Allo stesso tempo, grazie all’indice UNIQUE (user_id, group_id) in group_users e alla PRIMARY KEY (id) in users, il JOIN non crea record duplicati, quindi possiamo anche rimuovere DISTINCT dalla query, come mostrato di seguito:
SELECT
COUNT("directory_items"."id")
FROM
"directory_items"
LEFT OUTER JOIN "users" ON "users"."id" = "directory_items"."user_id"
LEFT OUTER JOIN "group_users" ON "group_users"."user_id" = "users"."id"
WHERE
"directory_items"."period_type" = 1
AND "group_users"."group_id" = 2898
Questa query ottimizzata impiega 941700 nanosecondi (miglioramento del 17,49%).
Eseguiamo i test in Discourse e dal Database rileviamo una query lenta (331729 nanosecondi, rispetto a una query simile) con un JOIN non necessario generato da ser_badge.rb#L18, come segue:
SELECT
COUNT(*)
FROM
"badges"
INNER JOIN "user_badges" ON "badges"."id" = "user_badges"."badge_id"
WHERE
"user_badges"."user_id" = 2112
AND (
user_badges.badge_id IN (
SELECT
id
FROM
badges
WHERE
enabled
)
)
AND "badges"."id" = 1
Questa subquery può essere sostituita da un semplice predicato badges.enabled, come mostrato di seguito:
SELECT
COUNT(*)
FROM
"badges"
INNER JOIN "user_badges" ON "badges"."id" = "user_badges"."badge_id"
WHERE
"user_badges"."user_id" = 2112
AND badges.enabled
AND "badges"."id" = 1
Questa query ottimizzata impiega 267383 nanosecondi (miglioramento del 19%).