diff --git a/includes/webform.report.inc b/includes/webform.report.inc
index 2669dd2..dc6ddbd 100644
--- a/includes/webform.report.inc
+++ b/includes/webform.report.inc
@@ -16,7 +16,8 @@ module_load_include('inc', 'webform', 'includes/webform.submissions');
 function webform_results_submissions($node, $user_filter, $pager_count) {
   global $user;
 
-  // Determine whether views or hard-coded tables should be used for the submissions table.
+  // Determine whether views or hard-coded tables should be used for the
+  // submissions table.
   if (!webform_variable_get('webform_table')) {
     // Load the submissions view
     $view = webform_get_view($node, 'webform_submissions');
@@ -213,8 +214,9 @@ function template_preprocess_webform_results_submissions(&$vars) {
  * Create a table containing all submitted values for a webform node.
  */
 function webform_results_table($node, $pager_count = 0) {
-  
-  // Determine whether views or hard-coded tables should be used for the submissions table.
+
+  // Determine whether views or hard-coded tables should be used for the
+  // submissions table.
   if (!webform_variable_get('webform_table')) {
     // Load and preview the results view with a node id argument
     $view = webform_get_view($node, 'webform_results');
@@ -502,19 +504,19 @@ function webform_results_download_form($form, &$form_state, $node) {
     '#maxlength' => 8,
   );
 
-  // If drafts are allowed, provide options to filter download based on draft status.
-  if ($node->webform['allow_draft'] || $node->webform['auto_save']) {
-    $form['range']['completion_type'] = array(
-      '#type' => 'radios',
-      '#title' => t('Included submissions'),
-      '#default_value' => 'all',
-      '#options' => array(
-        'all' => t('Finished and draft submissions'),
-        'finished' => t('Finished submissions only'),
-        'draft' => t('Drafts only'),
-      ),
-    );
-  }
+  // If drafts are allowed, provide options to filter download based on draft
+  // status.
+  $form['range']['completion_type'] = array(
+    '#type' => 'radios',
+    '#title' => t('Included submissions'),
+    '#default_value' => 'all',
+    '#options' => array(
+      'all' => t('Finished and draft submissions'),
+      'finished' => t('Finished submissions only'),
+      'draft' => t('Drafts only'),
+    ),
+    '#access' => ($node->webform['allow_draft'] || $node->webform['auto_save']),
+  );
 
   // By default results are downloaded. User can override this value if
   // programmatically submitting this form.
@@ -585,14 +587,23 @@ function webform_results_download_range_validate($element, $form_state) {
           }
         }
       }
-      // Check that the range will return something at all.
-      $form_state['values']['sids'] = webform_download_sids($form_state['values']['node']->nid, array('range_type' => $element['range_type']['#value'], 'start' => $element['start']['#value'], 'end' => $element['end']['#value']));
-      if (empty($form_state['values']['sids'])) {
-        form_error($element['start'], t('The specified range will not return any results.'));
-      }
       break;
   }
 
+  // Check that the range will return something at all.
+  $range_options = array(
+    'range_type' => $element['range_type']['#value'],
+    'start' => $element['start']['#value'],
+    'end' => $element['end']['#value'],
+    'latest' => $element['latest']['#value'],
+    'completion_type' => $element['completion_type']['#value'],
+    'batch_size' => 1,
+    'batch_number' => 0,
+  );
+  if (!webform_download_sids_count($form_state['values']['node']->nid, $range_options)) {
+    form_error($element['range_type'], t('The specified range will not return any results.'));
+  }
+
 }
 
 /**
@@ -602,16 +613,13 @@ function webform_results_download_range_after_build($element, &$form_state) {
   $node = $form_state['values']['node'];
 
   // Build a list of counts of new and total submissions.
-  $count = webform_get_submission_count($node->nid, NULL, NULL);
-  $sids = webform_download_sids($node->nid, array('range_type' => 'new'));
-
   $last_download = webform_download_last_download_info($node->nid);
 
   $element['#webform_download_info']['sid'] = $last_download ? $last_download['sid'] : 0;
   $element['#webform_download_info']['serial'] = $last_download ? $last_download['serial'] : NULL;
   $element['#webform_download_info']['requested'] = $last_download ? $last_download['requested'] : $node->created;
-  $element['#webform_download_info']['total'] = $count;
-  $element['#webform_download_info']['new'] = count($sids);
+  $element['#webform_download_info']['total'] = webform_get_submission_count($node->nid, NULL, NULL);
+  $element['#webform_download_info']['new'] = webform_download_sids_count($node->nid, array('range_type' => 'new'));
 
   return $element;
 }
@@ -720,15 +728,6 @@ function webform_results_download_form_submit(&$form, &$form_state) {
   $options += $defaults;
   $options['range'] += $defaults['range'];
 
-  // Use a pre-built list of SIDs provided by validate handlers (if any).
-  if (isset($form_state['values']['sids'])) {
-    $options['sids'] = $form_state['values']['sids'];
-  }
-  // Retrieve the list of required SIDs.
-  elseif ($options['range']['range_type'] != 'all') {
-    $options['sids'] = webform_download_sids($node->nid, $options['range']);
-  }
-
   // Determine an appropriate batch size based on the form and server specs.
   if (!isset($options['range']['batch_size'])) {
     // Start the batch size at 50,000 per batch, but divide by number of
@@ -742,12 +741,12 @@ function webform_results_download_form_submit(&$form, &$form_state) {
     $memory_modifier = max(1, ($memory_limit - (64 * $mb)) / (32 * $mb));
     $batch_size = ceil($batch_size * $memory_modifier);
 
-    // For time reasons, limit the batch size to 10,000.
-    $batch_size = min($batch_size, 10000);
+    // For time reasons, limit the batch size to 5,000.
+    $batch_size = min($batch_size, 5000);
 
     // Allow a non-UI configuration to override the batch size.
     $batch_size = variable_get('webform_export_batch_size', $batch_size);
-    
+
     $options['range']['batch_size'] = $batch_size;
   }
 
@@ -773,6 +772,9 @@ function _webform_export_tempname() {
  *
  * @see webform_results_export_batch()
  *
+ * @deprecated This function is schedule to be removed in webform 7.x-5.x. Use
+ * the batch opertions instead.
+ *
  * @return array|null
  *   The array of export info or null if the file could not be opened.
  */
@@ -828,7 +830,7 @@ function webform_results_export($node, $format = 'delimited', $options = array()
   $export_info['file_name'] = $file_name;
   $export_info['row_count'] = $row_count;
   $export_info['col_count'] = $col_count;
-  $export_info['last_sid'] = $sid;
+  $export_info['last_sid'] = end($submissions) ? key($submissions) : NULL;
   $export_info['exporter'] = $exporter;
 
   return $export_info;
@@ -930,6 +932,8 @@ function webform_results_download_headers($node, $options) {
 /**
  * Returns rows of downloadable webform data.
  *
+ * @deprecated This function is scheduled to be removed in webform 7.x-5.x.
+ *
  * @param $node
  *   The webform node on which to generate the analysis.
  * @param array $options
@@ -944,8 +948,6 @@ function webform_results_download_headers($node, $options) {
  *   by the super-global $_GET['page'] variable.
  */
 function webform_results_download_rows($node, $options, $serial_start = 0) {
-  module_load_include('inc', 'webform', 'includes/webform.components');
-
   // Get all the required submissions for the download.
   $filters['nid'] = $node->nid;
   if (isset($options['sids'])){
@@ -956,11 +958,37 @@ function webform_results_download_rows($node, $options, $serial_start = 0) {
   }
 
   $submissions = webform_get_submissions($filters, NULL);
+
+  return webform_results_download_rows_process($node, $options, $serial_start, $submissions);
+}
+
+/**
+ * Processes the submissions to be downloaded into exported rows.
+ *
+ * This is an internal routine and not intended for use by other modules.
+ *
+ * @param $node
+ *   The webform node on which to generate the analysis.
+ * @param array $options
+ *   A list of options that define the output format. These are generally passed
+ *   through from the GUI interface.
+ * @param $serial_start
+ *   The starting position for the Serial column in the output.
+ * @param array $submissions
+ *   An associative array of loaded submissions, indexed by sid.
+ *
+ * @return $rows
+ *   An array of rows built according to the provided $serial_start and
+ *   $pager_count variables. Note that the current page number is determined
+ *   by the super-global $_GET['page'] variable.
+ */
+function webform_results_download_rows_process($node, $options, $serial_start, $submissions) {
+  module_load_include('inc', 'webform', 'includes/webform.components');
+
   $submission_information = webform_results_download_submission_information($node, $options);
 
   // Generate a row for each submission.
   $row_count = 0;
-  $sid = 0;
   $rows = array();
   foreach ($submissions as $sid => $submission) {
     $row_count++;
@@ -971,7 +999,8 @@ function webform_results_download_rows($node, $options, $serial_start = 0) {
       $cell = module_invoke_all('webform_results_download_submission_information_data', $token, $submission, $options, $serial_start, $row_count);
       $context = array('token' => $token, 'submission' => $submission, 'options' => $options, 'serial_start' => $serial_start, 'row_count' => $row_count);
       drupal_alter('webform_results_download_submission_information_data', $cell, $context);
-      // implode() to ensure everything from a single value goes into one column, even if more than one module responds to this item.
+      // implode() to ensure everything from a single value goes into one
+      // column, even if more than one module responds to this item.
       $row[] = implode(', ', $cell);
     }
 
@@ -1133,17 +1162,14 @@ function webform_results_download($node, $export_info) {
   }
 
   // Update user last downloaded sid if required.
-  if ((isset($export_info['options']['sids']) || $export_info['options']['range']['range_type'] != 'range') && !empty($export_info['last_sid'])) {
-    // Delete existing record.
-    db_delete('webform_last_download')
-      ->condition('nid', $node->nid)
-      ->condition('uid', $user->uid)
-      ->execute();
-    // Write new record.
-    db_insert('webform_last_download')
-      ->fields(array(
+  if (!in_array($export_info['options']['range']['range_type'], array('range', 'range_serial')) && !empty($export_info['last_sid'])) {
+    // Insert a new record or update an existing record.
+    db_merge('webform_last_download')
+      ->key(array(
         'nid' => $node->nid,
         'uid' => $user->uid,
+      ))
+      ->fields(array(
         'sid' => $export_info['last_sid'],
         'requested' => REQUEST_TIME,
       ))
@@ -1236,19 +1262,30 @@ function webform_results_batch_headers($node, $format = 'delimited', $options =
 function webform_results_batch_rows($node, $format = 'delimited', $options = array(), &$context) {
   module_load_include('inc', 'webform', 'includes/webform.export');
 
+  // Initialize the sandbox if this is the first execution of the batch
+  // operation.
   if (!isset($context['sandbox']['batch_number'])) {
     $context['sandbox']['batch_number'] = 0;
-    $sids = isset($options['sids']) ? $options['sids'] : webform_download_sids($node->nid, $options['range']);
-    $context['sandbox']['sid_count'] = count($sids);
+    $context['sandbox']['sid_count'] = webform_download_sids_count($node->nid, $options['range']);
     $context['sandbox']['batch_max'] = max(1, ceil($context['sandbox']['sid_count'] / $options['range']['batch_size']));
     $context['sandbox']['serial'] = 0;
     $context['sandbox']['last_sid'] = 0;
   }
+
   // Retrieve the submissions for this batch process.
   $options['range']['batch_number'] = $context['sandbox']['batch_number'];
-  $sids = isset($options['sids']) ? $options['sids'] : webform_download_sids($node->nid, $options['range']);
-  $options['sids'] = $sids;
-  $rows = webform_results_download_rows($node, $options, $context['sandbox']['serial']);
+
+  $query = webform_download_sids_query($node->nid, $options['range']);
+
+  // Join to the users table to include user name in results, as required by
+  // webform_results_download_rows_process.
+  $query->leftJoin('users', 'u', 'u.uid = ws.uid');
+  $query->fields('u', array('name'));
+  $query->fields('ws');
+
+  $submissions = webform_get_submissions_load($query);
+
+  $rows = webform_results_download_rows_process($node, $options, $context['sandbox']['serial'], $submissions);
 
   // Write these submissions to the file.
   $exporter = webform_export_create_handler($format, $options);
@@ -1260,8 +1297,9 @@ function webform_results_batch_rows($node, $format = 'delimited', $options = arr
     $exporter->add_row($handle, $row, $context['results']['row_count']);
     $context['results']['row_count']++;
   }
-  $context['sandbox']['serial'] += count($sids);
-  $context['sandbox']['last_sid'] = end($sids);
+
+  $context['sandbox']['serial'] += count($submissions);
+  $context['sandbox']['last_sid'] = end($submissions) ? key($submissions) : NULL;
   $context['sandbox']['batch_number']++;
 
   @fclose($handle);
@@ -1603,10 +1641,46 @@ function theme_webform_results_analysis($variables) {
 
 /**
  * Given a set of range options, retrieve a set of SIDs for a webform node.
+ *
+ * @deprecated This function is scheduled to be removed int webform 7.x-5.x.
+ * Use webform_download_sids_query() instead.
+ *
  */
 function webform_download_sids($nid, $range_options, $uid = NULL) {
-  $query = db_select('webform_submissions', 'ws')
+  return webform_download_sids_query($nid, $range_options, $uid)
     ->fields('ws', array('sid'))
+    ->execute()
+    ->fetchCol();
+}
+
+/**
+ * Retrieves a count the number of matching submissions.
+ *
+ */
+function webform_download_sids_count($nid, $range_options, $uid = NULL) {
+  return webform_download_sids_query($nid, $range_options, $uid)
+    ->countQuery()
+    ->execute()
+    ->fetchField();
+}
+
+/**
+ * Given a set of range options, return an unexecuted select query.
+ *
+ * The query will have no fields as they should be added by the caller as
+ * desired.
+ *
+ * @param integer $nid
+ *   The node id of the webform.
+ * @param array $range_options
+ *   Associate array of range options.
+ * @param integer $uid
+ *   The user id of the user whose last download information should be used,
+ *   or the current user if NULL. This is unrelated to which user submitted
+ *   the submissions.
+ */
+function webform_download_sids_query($nid, $range_options, $uid = NULL) {
+  $query = db_select('webform_submissions', 'ws')
     ->condition('nid', $nid)
     ->addTag('webform_download_sids');
 
@@ -1651,27 +1725,10 @@ function webform_download_sids($nid, $range_options, $uid = NULL) {
     $query->condition('is_draft', (int) ($range_options['completion_type'] === 'draft'));
   }
 
-  // Set batch properties. Unfortunately the pager query class reads directly
-  // from $_GET['page'] to determine the current page number, so we have to set
-  // this super-global and then restore it after executing the query.
-  if (isset($range_options['batch_number']) && isset($range_options['batch_size'])) {
-    if (isset($_GET['page'])) {
-      $current_page = $_GET['page'];
-    }
-    $_GET['page'] = $range_options['batch_number'];
-    $query = $query->extend('PagerDefault');
-    $query->limit($range_options['batch_size']);
-    $query->element(0);
+  if (isset($range_options['batch_number']) && !empty($range_options['batch_size'])) {
+    $query->range($range_options['batch_number'] * $range_options['batch_size'], $range_options['batch_size']);
   }
-
-  $sids = $query->execute()->fetchCol();
-
-  // Restore the current page number (if any).
-  if (isset($current_page)) {
-    $_GET['page'] = $current_page;
-  }
-
-  return $sids;
+  return $query;
 }
 
 /**
diff --git a/includes/webform.submissions.inc b/includes/webform.submissions.inc
index 0b561f0..3408adc 100644
--- a/includes/webform.submissions.inc
+++ b/includes/webform.submissions.inc
@@ -271,7 +271,8 @@ function webform_submission_send_mail($node, $submission, $emails = NULL) {
         count($default_from_parts) == 2 &&
         $default_from_parts[1] &&
         stripos($email['from'], '@' . $default_from_parts[1]) === FALSE) {
-      // Message is not already being sent from the domain of the default webform from address.
+      // Message is not already being sent from the domain of the default
+      // webform from address.
       $email['headers']['Reply-To'] = $email['from'];
       // Match e-mails of the form 'My Name <email@domain.com>' as follows:
       // ^          = beginning of string
@@ -701,22 +702,42 @@ function webform_submission_render($node, $submission, $email, $format, $exclude
  *   Optional. The number of submissions to include in the results.
  */
 function webform_get_submissions($filters = array(), $header = NULL, $pager_count = 0) {
-  $submissions = array();
+  return webform_get_submissions_load(webform_get_submissions_query($filters, $header, $pager_count));
+}
 
+/**
+ * Returns an unexecuted webform_submissions query on for the arguments.
+ *
+ * This is an internal routine and not intended for use by other modules.
+ *
+ * @param $filters
+ *   An array of filters to apply to this query. Usually in the format
+ *   array('nid' => $nid, 'uid' => $uid). A single integer may also be passed
+ *   in, which will be equivalent to specifying a $nid filter. 'sid' may also
+ *   be included, either as a single sid or an array of sid's.
+ * @param $header
+ *   If the results of this fetch will be used in a sortable
+ *   table, pass the array header of the table.
+ * @param $pager_count
+ *   Optional. The number of submissions to include in the results.
+ */
+function webform_get_submissions_query($filters = array(), $header = NULL, $pager_count = 0) {
   if (!is_array($filters)) {
     $filters = array('ws.nid' => $filters);
   }
 
-  // UID filters need to be against a specific table.
-  if (isset($filters['uid'])) {
-    $filters['u.uid'] = $filters['uid'];
-    unset($filters['uid']);
+  // Qualify all filters with a table alias. ws.* is assumed, except for u.uid.
+  foreach ($filters as $column => $value) {
+    if (strpos($column, '.') === FALSE) {
+      $filters[($column == 'uid' ? 'u.' : 'ws.') . $column] = $value;
+      unset($filters[$column]);
+    }
   }
 
   // If the sid is specified, but there are none, force the query to fail
   // rather than query on an empty array
-  if (isset($filters['sid']) && empty($filters['sid'])) {
-    $filters['sid'] = 0;
+  if (isset($filters['ws.sid']) && empty($filters['ws.sid'])) {
+    $filters['ws.sid'] = 0;
   }
 
   // Build the list of submissions and load their basic information.
@@ -726,6 +747,8 @@ function webform_get_submissions($filters = array(), $header = NULL, $pager_coun
     ->distinct()
     ->addTag('webform_get_submissions_sids')
     ->fields('ws');
+
+  // Add each filter.
   foreach ($filters as $column => $value) {
     $pager_query->condition($column, $value);
   }
@@ -736,10 +759,10 @@ function webform_get_submissions($filters = array(), $header = NULL, $pager_coun
   if (isset($filters['u.uid']) && $filters['u.uid'] === 0) {
     if (!empty($_SESSION['webform_submission'])) {
       $anonymous_sids = array_keys($_SESSION['webform_submission']);
-      $pager_query->condition('sid', $anonymous_sids, 'IN');
+      $pager_query->condition('ws.sid', $anonymous_sids, 'IN');
     }
     else {
-      $pager_query->condition('sid', 0);
+      $pager_query->condition('ws.sid', 0);
     }
   }
 
@@ -749,7 +772,8 @@ function webform_get_submissions($filters = array(), $header = NULL, $pager_coun
       $metadata_columns[] = $header_item['data'];
     }
     $sort = drupal_get_query_parameters();
-    // Sort by submitted data column if order is set but not in $metadata_columns.
+    // Sort by submitted data column if order is set but not in
+    // $metadata_columns.
     if (isset($sort['order']) && !in_array($sort['order'], $metadata_columns, TRUE)) {
       // Default if sort is unset or invalid.
       if (!isset($sort['sort']) || !in_array($sort['sort'], array('asc', 'desc'), TRUE)) {
@@ -758,7 +782,7 @@ function webform_get_submissions($filters = array(), $header = NULL, $pager_coun
       $pager_query->leftJoin('webform_component', 'wc', 'ws.nid = wc.nid AND wc.name = :form_key', array('form_key' => $sort['order']));
       $pager_query->leftJoin('webform_submitted_data', 'wsd', 'wc.nid = wsd.nid AND ws.sid = wsd.sid AND wc.cid = wsd.cid');
       $pager_query->orderBy('wsd.data', $sort['sort']);
-      $pager_query->orderBy('sid', 'ASC');
+      $pager_query->orderBy('ws.sid', 'ASC');
     }
     // Sort by metadata column.
     else {
@@ -768,7 +792,7 @@ function webform_get_submissions($filters = array(), $header = NULL, $pager_coun
     }
   }
   else {
-    $pager_query->orderBy('sid', 'ASC');
+    $pager_query->orderBy('ws.sid', 'ASC');
   }
 
   if ($pager_count) {
@@ -776,35 +800,75 @@ function webform_get_submissions($filters = array(), $header = NULL, $pager_coun
     $pager_query = $pager_query->extend('PagerDefault');
     $pager_query->limit($pager_count);
   }
+  return $pager_query;
+}
+
+/**
+ * Retrieve and load the submissions for the specified submissions query.
+ *
+ * This is an internal routine and not intended for use by other modules.
+ *
+ * @params object $pager_query
+ *   A select or extended select query containing the needed fields:
+ *     webform_submissions: all fields
+ *     user: name
+ * @return array
+ *   An array of loaded webform submissions.
+ */
+function webform_get_submissions_load($pager_query) {
+  // If the "$pager_query" is actually an unextended select query, then instead
+  // of querying the webform_submissions_data table with a pontentially huge
+  // array of sids in an IN clause, use the select query directy as this will
+  // be much faster. Extended queries don't work in join clauses. The query
+  // is assumed to include the sid.
+  if ($pager_query instanceof SelectQuery) {
+    $submissions_query = clone $pager_query;
+  }
+
+  // Extract any filter on node id to use in an optimization below
+  foreach ($pager_query->conditions() as $index => $condition) {
+    if ($index !== '#conjunction' && $condition['operator'] === '=' && ($condition['field'] === 'nid' || $condition['field'] === 'ws.nid')) {
+      $nid = $condition['value'];
+      break;
+    }
+  }
+
   $result = $pager_query->execute();
   $submissions = $result->fetchAllAssoc('sid');
 
-  $sids = array();
+  // If there are no submissions being retrieved, return an empty array.
+  if (!$submissions) {
+    return $submissions;
+  }
+
   foreach ($submissions as $sid => $submission) {
-    $sids[] = $sid;
     $submissions[$sid]->preview = FALSE;
     $submissions[$sid]->data = array();
   }
 
-  // If there are no submissions being retrieved, return an empty array.
-  if (empty($sids)) {
-    return $submissions;
-  }
-
   // Query the required submission data.
   $query = db_select('webform_submitted_data', 'sd');
   $query
     ->addTag('webform_get_submissions_data')
     ->fields('sd', array('sid', 'cid', 'no', 'data'))
-    ->condition('sd.sid', $sids, 'IN')
     ->orderBy('sd.sid', 'ASC')
     ->orderBy('sd.cid', 'ASC')
     ->orderBy('sd.no', 'ASC');
 
+  if (isset($submissions_query)) {
+    // If available, prefer joining on the subquery as it is much faster than an
+    // IN clause on a large array. A subquery with the IN operator doesn't work
+    // when the subquery has a LIMIT clause, requiring an inner join instead.
+    $query->innerJoin($submissions_query, 'ws2', 'sd.sid = ws2.sid');
+  }
+  else {
+    $query->condition('sd.sid',  array_keys($submissions), 'IN');
+  }
+
   // By adding the NID to this query we allow MySQL to use the primary key on
   // in webform_submitted_data for sorting (nid_sid_cid_no).
-  if (isset($filters['nid'])) {
-    $query->condition('sd.nid', $filters['nid']);
+  if (isset($nid)) {
+    $query->condition('sd.nid', $nid);
   }
 
   $result = $query->execute();
@@ -937,7 +1001,8 @@ function webform_submission_user_limit_check($node, $account = NULL) {
     $query->condition('remote_addr', ip_address());
   }
 
-  // Fetch all the entries from the database within the submit interval with this username and IP.
+  // Fetch all the entries from the database within the submit interval with
+  // this username and IP.
   $num_submissions_database = 0;
   if ($account->uid !== 0 || $tracking_mode === 'ip_address' || $tracking_mode === 'strict') {
     $num_submissions_database = $query->countQuery()->execute()->fetchField();
