I wrote this query back in <[EMAIL PROTECTED]>

I also suggested the alternative:

select count(*), count(case when seen_flag > 0 Then 1 else null end),
count(case when recent_flag > 0 then 1 else null end) FROM
dbmail_messages WHERE status < '2' and mailbox_idnr='1';

in <[EMAIL PROTECTED]>

but I cannot recall if I ever profiled that with MySQL.

In SQLite, it generates a plan where the database isn't touched (index
only).


On Mon, 2005-12-12 at 10:03 -0800, Kevin Brown wrote:
> In experimenting with DBMail 2.0.7 (even with the changes I submitted
> regarding the headername query, I can't get 2.1-trunk to run well
> enough to work properly with both Mutt and Thunderbird), I discovered
> (as perhaps some of you have) that storing changes to the attributes
> is very slow when the folder has lots of messages (tens of thousands).
> 
> 2.1.x may have fixed this, though the query in question is the same.
> But in 2.0.7, the following query is responsible for the bulk of the
> time spent in the update:
> 
>     SELECT 'a',COUNT(*) FROM dbmail_messages WHERE mailbox_idnr='14'
>       AND (status='0' OR status='1') UNION SELECT 'b',COUNT(*) FROM
>       dbmail_messages WHERE mailbox_idnr='14' AND (status='0' OR
>       status='1') AND seen_flag=1 UNION SELECT 'c',COUNT(*) FROM
>       dbmail_messages WHERE mailbox_idnr='14' AND (status='0' OR
>       status='1') AND recent_flag=1
> 
> (substitute any given mailbox id for '14', of course).
> 
> 
> This query originates from this bit of code here in function
> db_getmailbox in db.c:
> 
>     snprintf(query, DEF_QUERYSIZE,
>       "SELECT 'a',COUNT(*) FROM dbmail_messages WHERE mailbox_idnr='%llu' "
>       "AND (status='%d' OR status='%d') UNION "
>       "SELECT 'b',COUNT(*) FROM dbmail_messages WHERE mailbox_idnr='%llu' "
>       "AND (status='%d' OR status='%d') AND seen_flag=1 UNION "
>       "SELECT 'c',COUNT(*) FROM dbmail_messages WHERE mailbox_idnr='%llu' "
>       "AND (status='%d' OR status='%d') AND recent_flag=1", 
>       mb->uid, MESSAGE_STATUS_NEW, MESSAGE_STATUS_SEEN,
>       mb->uid, MESSAGE_STATUS_NEW, MESSAGE_STATUS_SEEN,
>       mb->uid, MESSAGE_STATUS_NEW, MESSAGE_STATUS_SEEN);
>             
> 
> 
> That query is horribly inefficient on PostgreSQL.  The following is
> much more efficient (by at least a factor of two if not more) and
> accomplishes the same thing, though it will require a bit more
> interpretation by our code to use:
> 
>     SELECT seen_flag*2 + recent_flag, count(*) FROM dbmail_messages
>       WHERE mailbox_idnr = '14' AND status IN ('0', '1') GROUP BY
>       seen_flag*2 + recent_flag;
> 
> We can generate it thusly:
> 
>     snprintf(query, DEF_QUERYSIZE,
>       "SELECT seen_flag*2 + recent_flag, count(*) FROM "
>       "dbmail_messages WHERE mailbox_idnr = '%d' AND status IN "
>       "('%d', '%d') GROUP BY seen_flag*2 + recent_flag",
>       mb->uid, MESSAGE_STATUS_NEW, MESSAGE_STATUS_SEEN);
> 
> The query will return up to 4 rows with the following values as the
> first column:
> 
>     0: neither seen_flag nor recent_flag is set.
>     1: recent_flag is set, seen_flag is not.
>     2: seen_flag is set, recent_flag is not.
>     3: both seen_flag and recent_flag are set
> 
> 
> The additional interpretation is in reading and interpreting the
> results.  The query won't return rows for flag combinations that don't
> have any corresponding entries, so we have to infer those.  I think
> the most efficient way to do that is to create a simple 4-element
> integer array, initialize its elements to zero, and use the first
> column results as the index to that array.  Place the results in the
> array.  The array will have zero for entries that weren't returned by
> the query.  The array definition will be something like:
> 
>     unsigned int results[4];
> 
> 
> The values we're interested in (exists, seen, recent) will then be:
> 
>     exists = results[0] + results[1] + results[2] + results[3];
>     seen = results[2] + results[3];
>     recent = results[1] + results[3]
> 
> 
> 
> Now, the above is more efficient, but the real win would be if we
> could avoid calling db_getmailbox (or perhaps a modified version of it
> that doesn't have to do any of this) altogether after doing a STORE.
> 
> 
> I don't know how much more (if any) efficient the above will be on
> MySQL or SQLite, but I wouldn't expect it to be less efficient.
> 
> 
> Thoughts?
> 
> 
> 

-- 
Internet Connection High Quality Web Hosting
http://www.internetconnection.net/

Reply via email to