From 27887d0141333200e735870d63e869abb2100a3f Mon Sep 17 00:00:00 2001 From: Holger Just Date: Thu, 19 Jan 2023 14:25:51 +0100 Subject: [PATCH] Improve index usability for Project#project_condition Previously, the query used both the projects.id as well as the projects.lft / project.rgt columns. This caused MySQL to perform a table scan on the (large) issues table followed by an index-scan on the projects table. For the common case where there are more issues / time entries than projects, this resulted in an sub-optimal query plan for issues / time entries in those projects, causing a query time of > 5 seconds for a time entry tally of a single project on a large Redmine installation. With this change, MySQL first filters the projects followed by the issues/ time entries. This allows MySQL to use the project_id index on the issues table after performing a table scan on the (smaller) projects table. The equivalent query finishes in 50ms (10 times faster). The new query is equivalent to Project.self_and_descendants, as was semantically the old one. --- app/models/project.rb | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 80bea61d97..43a55428e4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -329,15 +329,17 @@ def commit_logtime_activity # Returns a :conditions SQL string that can be used to find the issues associated with this project. # # Examples: - # project.project_condition(true) => "(projects.id = 1 OR (projects.lft > 1 AND projects.rgt < 10))" + # project.project_condition(true) => "(projects.lft >= 1 AND projects.rgt <= 10)" # project.project_condition(false) => "projects.id = 1" def project_condition(with_subprojects) - cond = "#{Project.table_name}.id = #{id}" if with_subprojects - cond = "(#{cond} OR (#{Project.table_name}.lft > #{lft} AND " \ - "#{Project.table_name}.rgt < #{rgt}))" + "(" \ + "#{Project.table_name}.lft >= #{lft} AND " \ + "#{Project.table_name}.rgt <= #{rgt}" \ + ")" + else + "#{Project.table_name}.id = #{id}" end - cond end def self.find(*args) -- 2.34.0