diff --git a/include/class.orm.php b/include/class.orm.php
index 44d292f664038f825478fc01bc6e2e848e78e4aa..9798d94819db755e51664e83e739b4ec4dfde6f6 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -1069,6 +1069,10 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl
         return $this;
     }
 
+    function copy() {
+        return clone $this;
+    }
+
     function all() {
         return $this->getIterator()->asArray();
     }
@@ -2233,10 +2237,13 @@ class MySqlCompiler extends SqlCompiler {
             $vals = array_map(array($this, 'input'), $b);
             $b = '('.implode(', ', $vals).')';
         }
+        // MySQL is almost always faster with a join. Use one if possible
         // MySQL doesn't support LIMIT or OFFSET in subqueries. Instead, add
         // the query as a JOIN and add the join constraint into the WHERE
         // clause.
-        elseif ($b instanceof QuerySet && ($b->isWindowed() || $b->countSelectFields() > 1)) {
+        elseif ($b instanceof QuerySet
+            && ($b->isWindowed() || $b->countSelectFields() > 1 || $b->chain)
+        ) {
             $f1 = $b->values[0];
             $view = $b->asView();
             $alias = $this->pushJoin($view, $a, $view, array('constraint'=>array()));
diff --git a/include/client/tickets.inc.php b/include/client/tickets.inc.php
index 32ad5abf8fc8def44985e6b56214b5fad6b7d613..fef80c6008f3bae8555da341d00dd55a237e316e 100644
--- a/include/client/tickets.inc.php
+++ b/include/client/tickets.inc.php
@@ -32,23 +32,11 @@ else {
     $closedTickets = $thisclient->getNumClosedTickets($org_tickets);
 }
 
-$tickets = TicketModel::objects();
+$tickets = Ticket::objects();
 
 $qs = array();
 $status=null;
 
-if ($settings['status'])
-    $status = strtolower($settings['status']);
-    switch ($status) {
-    default:
-        $status = 'open';
-    case 'open':
-    case 'closed':
-		$results_type = ($status == 'closed') ? __('Closed Tickets') : __('Open Tickets');
-        $tickets->filter(array('status__state' => $status));
-        break;
-}
-
 $sortOptions=array('id'=>'number', 'subject'=>'cdata__subject',
                     'status'=>'status__name', 'dept'=>'dept__name','date'=>'created');
 $orderWays=array('DESC'=>'-','ASC'=>'');
@@ -65,16 +53,40 @@ if($_REQUEST['order'] && $orderWays[strtoupper($_REQUEST['order'])])
 $x=$sort.'_sort';
 $$x=' class="'.strtolower($_REQUEST['order'] ?: 'desc').'" ';
 
-// Add visibility constraints
-$visibility = Q::any(array(
-    'user_id' => $thisclient->getId(),
-    'thread__collaborators__user_id' => $thisclient->getId(),
-));
+$basic_filter = Ticket::objects();
+if ($settings['topic_id']) {
+    $basic_filter = $basic_filter->filter(array('topic_id' => $settings['topic_id']));
+}
+
+if ($settings['status'])
+    $status = strtolower($settings['status']);
+    switch ($status) {
+    default:
+        $status = 'open';
+    case 'open':
+    case 'closed':
+		$results_type = ($status == 'closed') ? __('Closed Tickets') : __('Open Tickets');
+        $basic_filter->filter(array('status__state' => $status));
+        break;
+}
 
-if ($thisclient->canSeeOrgTickets())
-    $visibility->add(array('user__org_id' => $thisclient->getOrgId()));
+// Add visibility constraints — use a union query to use multiple indexes,
+// use UNION without "ALL" (false as second parameter to union()) to imply
+// unique values
+$visibility = $basic_filter->copy()
+    ->values_flat('ticket_id')
+    ->filter(array('user_id' => $thisclient->getId()))
+    ->union($basic_filter->copy()
+        ->values_flat('ticket_id')
+        ->filter(array('thread__collaborators__user_id' => $thisclient->getId()))
+    , false);
 
-$tickets->filter($visibility);
+if ($thisclient->canSeeOrgTickets()) {
+    $visibility = $visibility->union(
+        $basic_filter->copy()->values_flat('ticket_id')
+            ->filter(array('user__org_id' => $thisclient->getOrgId()))
+    , false);
+}
 
 // Perform basic search
 if ($settings['keywords']) {
@@ -83,14 +95,10 @@ if ($settings['keywords']) {
         $tickets->filter(array('number__startswith'=>$q));
     } else { //Deep search!
         // Use the search engine to perform the search
-        $tickets = $ost->searcher->find($q, $tickets);
+        $tickets = $ost->searcher->find($q, $tickets)->distinct('ticket_id');
     }
 }
 
-if ($settings['topic_id']) {
-    $tickets = $tickets->filter(array('topic_id' => $settings['topic_id']));
-}
-
 TicketForm::ensureDynamicDataView();
 
 $total=$tickets->count();
@@ -99,6 +107,7 @@ $pageNav=new Pagenate($total, $page, PAGE_LIMIT);
 $qstr = '&'. Http::build_query($qs);
 $qs += array('sort' => $_REQUEST['sort'], 'order' => $_REQUEST['order']);
 $pageNav->setURL('tickets.php', $qs);
+$tickets->filter(array('ticket_id__in' => $visibility));
 $pageNav->paginate($tickets);
 
 $showing =$total ? $pageNav->showing() : "";
@@ -112,7 +121,6 @@ if($search)
 
 $negorder=$order=='-'?'ASC':'DESC'; //Negate the sorting
 
-$tickets->order_by($order.$order_by);
 $tickets->values(
     'ticket_id', 'number', 'created', 'isanswered', 'source', 'status_id',
     'status__state', 'status__name', 'cdata__subject', 'dept_id',