diff --git a/include/class.orm.php b/include/class.orm.php
index 662349227c5bc7ac9c0cd33ab5c27c3102d99455..761f91dee25bf683896e26582d449ee2c56e7df3 100644
--- a/include/class.orm.php
+++ b/include/class.orm.php
@@ -1111,6 +1111,7 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl
 
     const OPT_NOSORT    = 'nosort';
     const OPT_NOCACHE   = 'nocache';
+    const OPT_MYSQL_FOUND_ROWS = 'found_rows';
 
     const ITER_MODELS   = 1;
     const ITER_HASH     = 2;
@@ -1122,6 +1123,7 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl
 
     var $query;
     var $count;
+    var $total;
 
     function __construct($model) {
         $this->model = $model;
@@ -1315,6 +1317,42 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl
         return $this->count = $compiler->compileCount($this);
     }
 
+    /**
+     * Similar to count, except that the LIMIT and OFFSET parts are not
+     * considered in the counts. That is, this will return the count of rows
+     * if the query were not windowed with limit() and offset().
+     *
+     * For MySQL, the query will be submitted and fetched and the
+     * SQL_CALC_FOUND_ROWS hint will be sent in the query. Afterwards, the
+     * result of FOUND_ROWS() is fetched and is the result of this function.
+     *
+     * The result of this function is cached. If further changes are made
+     * after this is run, the changes should be made in a clone.
+     */
+    function total() {
+        // Optimize the query with the CALC_FOUND_ROWS if
+        // - the compiler supports it
+        // - the iterator hasn't yet been built, that is, the query for this
+        //   statement has not yet been sent to the database
+        $compiler = $this->compiler;
+        if ($compiler::supportsOption(self::OPT_MYSQL_FOUND_ROWS)
+            && !isset($this->_iterator)
+        ) {
+            // This optimization requires caching
+            $this->options(array(
+                self::OPT_MYSQL_FOUND_ROWS => 1,
+                self::OPT_NOCACHE => null,
+            ));
+            $this->exists(true);
+            $compiler = new $compiler();
+            return $this->total = $compiler->getFoundRows();
+        }
+
+        $query = clone $this;
+        $query->limit(false)->offset(false)->order_by(false);
+        return $this->total = $query->count();
+    }
+
     function toSql($compiler, $model, $alias=false) {
         // FIXME: Force root model of the compiler to $model
         $exec = $this->getQuery(array('compiler' => get_class($compiler),
@@ -1431,6 +1469,7 @@ class QuerySet implements IteratorAggregate, ArrayAccess, Serializable, Countabl
         unset($this->_iterator);
         unset($this->query);
         unset($this->count);
+        unset($this->total);
     }
 
     function __call($name, $args) {
@@ -1556,6 +1595,7 @@ EOF;
         unset($info['offset']);
         unset($info['_iterator']);
         unset($info['count']);
+        unset($info['total']);
         return serialize($info);
     }
 
@@ -2761,6 +2801,10 @@ class MySqlCompiler extends SqlCompiler {
         return sprintf("`%s`", str_replace("`", "``", $what));
     }
 
+    function supportsOption($option) {
+        return true;
+    }
+
     /**
      * getWhereClause
      *
@@ -2803,6 +2847,12 @@ class MySqlCompiler extends SqlCompiler {
         return is_array($row) ? (int) $row[0] : null;
     }
 
+    function getFoundRows() {
+        $exec = new MysqlExecutor('SELECT FOUND_ROWS()', array());
+        $row = $exec->getRow();
+        return is_array($row) ? (int) $row[0] : null;
+    }
+
     function compileSelect($queryset) {
         $model = $queryset->model;
         // Use an alias for the root model table
@@ -2971,7 +3021,10 @@ class MySqlCompiler extends SqlCompiler {
 
         $joins = $this->getJoins($queryset);
 
-        $sql = 'SELECT '.implode(', ', $fields).' FROM '
+        $sql = 'SELECT ';
+        if ($queryset->hasOption(QuerySet::OPT_MYSQL_FOUND_ROWS))
+            $sql .= 'SQL_CALC_FOUND_ROWS ';
+        $sql .= implode(', ', $fields).' FROM '
             .$table.$joins.$where.$group_by.$having.$sort;
         // UNIONS
         if ($queryset->chain) {
diff --git a/include/staff/templates/queue-tickets.tmpl.php b/include/staff/templates/queue-tickets.tmpl.php
index 0cf3ad09d44bc37a171d67a6bf2f206abc5a0546..664a8d6e151fce4312b5af48c484f55bebc80f6a 100644
--- a/include/staff/templates/queue-tickets.tmpl.php
+++ b/include/staff/templates/queue-tickets.tmpl.php
@@ -26,12 +26,6 @@ if (!$view_all_tickets) {
     $tickets->filter($visibility);
 }
 
-$page = ($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1;
-$count = count($tickets);
-$pageNav = new Pagenate($count, $page, PAGE_LIMIT);
-$pageNav->setURL('tickets.php', $args);
-$tickets = $pageNav->paginate($tickets);
-
 // Make sure the cdata materialized view is available
 TicketForm::ensureDynamicDataView();
 
@@ -199,6 +193,12 @@ foreach ($columns as $C) {
   </thead>
   <tbody>
 <?php
+$page = ($_GET['p'] && is_numeric($_GET['p']))?$_GET['p']:1;
+$count = $tickets->total();
+$pageNav = new Pagenate($count, $page, PAGE_LIMIT);
+$pageNav->setURL('tickets.php', $args);
+$tickets = $pageNav->paginate($tickets);
+
 foreach ($tickets as $T) {
     echo '<tr>';
     if ($canManageTickets) { ?>