From 60fcf00cc2ff9893e0f542520cf8ee13b3820995 Mon Sep 17 00:00:00 2001
From: Jared Hancock <jared@osticket.com>
Date: Sat, 5 Oct 2013 20:00:17 -0500
Subject: [PATCH] Crazy performance penalty scanning blob tables

When scanning the file_chunk table for orphaned file chunks that can be
deleted, apparently, MySQL will read (at least part of) the blob data from
the disk. For databases with lots of large attachments, this can take
considerable time. Considering that it is triggered from the autocron and
will run everytime the cron is run, the database will spend considerable
time scanning for rows to be cleaned.

This patch changes the orphan cleanup into two phases. The first will search
just for the pk's of file chunks to be deleted. If any are found, then the
chunks are deleted by the file_id and chunk_id, which is the primary key of
the table.

The SELECT query seems to run at least 20 times faster than the delete
statement, and DELETEing against the primary key of the blob table should
be the fastest possible operation. Somehow, both queries required a full
table scan; however, because the SELECT statement is explictly only
interested in two fields, it is more clear to the query optimizer that the
blob data should not be scanned.

References:
http://stackoverflow.com/q/9511476
---
 include/class.file.php | 13 ++++++++++---
 1 file changed, 10 insertions(+), 3 deletions(-)

diff --git a/include/class.file.php b/include/class.file.php
index a896d9e9c..d53ff3d50 100644
--- a/include/class.file.php
+++ b/include/class.file.php
@@ -402,12 +402,19 @@ class AttachmentChunkedData {
     }
 
     function deleteOrphans() {
-
-        $sql = 'DELETE c.* FROM '.FILE_CHUNK_TABLE.' c '
+        $deleted = 0;
+        $sql = 'SELECT c.file_id, c.chunk_id FROM '.FILE_CHUNK_TABLE.' c '
              . ' LEFT JOIN '.FILE_TABLE.' f ON(f.id=c.file_id) '
              . ' WHERE f.id IS NULL';
 
-        return db_query($sql)?db_affected_rows():0;
+        $res = db_query($sql);
+        while (list($file_id, $chunk_id) = db_fetch_row($res)) {
+            db_query('DELETE FROM '.FILE_CHUNK_TABLE
+                .' WHERE file_id='.db_input($file_id)
+                .' AND chunk_id='.db_input($chunk_id));
+            $deleted += db_affected_rows();
+        }
+        return $deleted;
     }
 }
 ?>
-- 
GitLab