diff --git a/includes/database/database.inc b/includes/database/database.inc
index 90a3f74..d154df7 100644
--- a/includes/database/database.inc
+++ b/includes/database/database.inc
@@ -296,6 +296,13 @@ abstract class DatabaseConnection extends PDO {
    */
   protected $prefixReplace = array();
 
+  /**
+   * List of un-prefixed table names, keyed by prefixed table names.
+   *
+   * @var array
+   */
+  protected $unprefixedTablesMap = array();
+
   function __construct($dsn, $username, $password, $driver_options = array()) {
     // Initialize and prepare the connection prefix.
     $this->setPrefix(isset($this->connectionOptions['prefix']) ? $this->connectionOptions['prefix'] : '');
@@ -324,7 +331,9 @@ abstract class DatabaseConnection extends PDO {
     // Destroy all references to this connection by setting them to NULL.
     // The Statement class attribute only accepts a new value that presents a
     // proper callable, so we reset it to PDOStatement.
-    $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array()));
+    if (!empty($this->statementClass)) {
+      $this->setAttribute(PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array()));
+    }
     $this->schema = NULL;
   }
 
@@ -428,6 +437,13 @@ abstract class DatabaseConnection extends PDO {
     $this->prefixReplace[] = $this->prefixes['default'];
     $this->prefixSearch[] = '}';
     $this->prefixReplace[] = '';
+
+    // Set up a map of prefixed => un-prefixed tables.
+    foreach ($this->prefixes as $table_name => $prefix) {
+      if ($table_name !== 'default') {
+        $this->unprefixedTablesMap[$prefix . $table_name] = $table_name;
+      }
+    }
   }
 
   /**
@@ -464,6 +480,17 @@ abstract class DatabaseConnection extends PDO {
   }
 
   /**
+   * Gets a list of individually prefixed table names.
+   *
+   * @return array
+   *   An array of un-prefixed table names, keyed by their fully qualified table
+   *   names (i.e. prefix + table_name).
+   */
+  public function getUnprefixedTablesMap() {
+    return $this->unprefixedTablesMap;
+  }
+
+  /**
    * Prepares a query string and returns the prepared statement.
    *
    * This method caches prepared statements, reusing them when
@@ -2784,7 +2811,6 @@ function db_field_exists($table, $field) {
  *
  * @param $table_expression
  *   An SQL expression, for example "simpletest%" (without the quotes).
- *   BEWARE: this is not prefixed, the caller should take care of that.
  *
  * @return
  *   Array, both the keys and the values are the matching tables.
diff --git a/includes/database/schema.inc b/includes/database/schema.inc
index d8344c6..ca4cb1a 100644
--- a/includes/database/schema.inc
+++ b/includes/database/schema.inc
@@ -166,6 +166,11 @@ require_once dirname(__FILE__) . '/query.inc';
 
 abstract class DatabaseSchema implements QueryPlaceholderInterface {
 
+  /**
+   * The database connection.
+   *
+   * @var DatabaseConnection
+   */
   protected $connection;
 
   /**
@@ -322,25 +327,62 @@ abstract class DatabaseSchema implements QueryPlaceholderInterface {
   }
 
   /**
-   * Find all tables that are like the specified base table name.
+   * Finds all tables that are like the specified base table name.
    *
-   * @param $table_expression
-   *   An SQL expression, for example "simpletest%" (without the quotes).
-   *   BEWARE: this is not prefixed, the caller should take care of that.
+   * @param string $table_expression
+   *   An SQL expression, for example "cache_%" (without the quotes).
    *
-   * @return
-   *   Array, both the keys and the values are the matching tables.
+   * @return array
+   *   Both the keys and the values are the matching tables.
    */
   public function findTables($table_expression) {
-    $condition = $this->buildTableNameCondition($table_expression, 'LIKE', FALSE);
-
+    // Load all the tables up front in order to take into account per-table
+    // prefixes. The actual matching is done at the bottom of the method.
+    $condition = $this->buildTableNameCondition('%', 'LIKE');
     $condition->compile($this->connection, $this);
+
+    $individually_prefixed_tables = $this->connection->getUnprefixedTablesMap();
+    $default_prefix = $this->connection->tablePrefix();
+    $default_prefix_length = strlen($default_prefix);
+    $tables = array();
     // Normally, we would heartily discourage the use of string
     // concatenation for conditionals like this however, we
     // couldn't use db_select() here because it would prefix
     // information_schema.tables and the query would fail.
     // Don't use {} around information_schema.tables table.
-    return $this->connection->query("SELECT table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments())->fetchAllKeyed(0, 0);
+    $results = $this->connection->query("SELECT table_name FROM information_schema.tables WHERE " . (string) $condition, $condition->arguments());
+    foreach ($results as $table) {
+      // Take into account tables that have an individual prefix.
+      if (isset($individually_prefixed_tables[$table->table_name])) {
+        $prefix_length = strlen($this->connection->tablePrefix($individually_prefixed_tables[$table->table_name]));
+      }
+      elseif ($default_prefix && substr($table->table_name, 0, $default_prefix_length) !== $default_prefix) {
+        // This table name does not start the default prefix, which means that
+        // it is not managed by Drupal so it should be excluded from the result.
+        continue;
+      }
+      else {
+        $prefix_length = $default_prefix_length;
+      }
+
+      // Remove the prefix from the returned tables.
+      $unprefixed_table_name = substr($table->table_name, $prefix_length);
+
+      // The pattern can match a table which is the same as the prefix. That
+      // will become an empty string when we remove the prefix, which will
+      // probably surprise the caller, besides not being a prefixed table. So
+      // remove it.
+      if (!empty($unprefixed_table_name)) {
+        $tables[$unprefixed_table_name] = $unprefixed_table_name;
+      }
+    }
+
+    // Convert the table expression from its SQL LIKE syntax to a regular
+    // expression and escape the delimiter that will be used for matching.
+    $table_expression = str_replace(array('%', '_'), array('.*?', '.'), preg_quote($table_expression, '/'));
+    $tables = preg_grep('/^' . $table_expression . '$/i', $tables);
+
+    return $tables;
   }
 
   /**
diff --git a/includes/database/sqlite/database.inc b/includes/database/sqlite/database.inc
index 8a5ba8c..5e633f6 100644
--- a/includes/database/sqlite/database.inc
+++ b/includes/database/sqlite/database.inc
@@ -144,6 +144,18 @@ class DatabaseConnection_sqlite extends DatabaseConnection {
   }
 
   /**
+   * Gets all the attached databases.
+   *
+   * @return array
+   *   An array of attached database names.
+   *
+   * @see DatabaseConnection_sqlite::__construct().
+   */
+  public function getAttachedDatabases() {
+    return $this->attachedDatabases;
+  }
+
+  /**
    * SQLite compatibility implementation for the IF() SQL function.
    */
   public function sqlFunctionIf($condition, $expr1, $expr2 = NULL) {
diff --git a/includes/database/sqlite/schema.inc b/includes/database/sqlite/schema.inc
index df19d2f..a969e72 100644
--- a/includes/database/sqlite/schema.inc
+++ b/includes/database/sqlite/schema.inc
@@ -668,16 +668,30 @@ class DatabaseSchema_sqlite extends DatabaseSchema {
     $this->alterTable($table, $old_schema, $new_schema);
   }
 
+  /**
+   * {@inheritdoc}
+   */
   public function findTables($table_expression) {
-    // Don't add the prefix, $table_expression already includes the prefix.
-    $info = $this->getPrefixInfo($table_expression, FALSE);
-
-    // Can't use query placeholders for the schema because the query would have
-    // to be :prefixsqlite_master, which does not work.
-    $result = db_query("SELECT name FROM " . $info['schema'] . ".sqlite_master WHERE type = :type AND name LIKE :table_name", array(
-      ':type' => 'table',
-      ':table_name' => $info['table'],
-    ));
-    return $result->fetchAllKeyed(0, 0);
+    $tables = array();
+
+    // The SQLite implementation doesn't need to use the same filtering strategy
+    // as the parent one because individually prefixed tables live in their own
+    // schema (database), which means that neither the main database nor any
+    // attached one will contain a prefixed table name, so we just need to loop
+    // over all known schemas and filter by the user-supplied table expression.
+    $attached_dbs = $this->connection->getAttachedDatabases();
+    foreach ($attached_dbs as $schema) {
+      // Can't use query placeholders for the schema because the query would
+      // have to be :prefixsqlite_master, which does not work. We also need to
+      // ignore the internal SQLite tables.
+      $result = db_query("SELECT name FROM " . $schema . ".sqlite_master WHERE type = :type AND name LIKE :table_name AND name NOT LIKE :pattern", array(
+        ':type' => 'table',
+        ':table_name' => $table_expression,
+        ':pattern' => 'sqlite_%',
+      ));
+      $tables += $result->fetchAllKeyed(0, 0);
+    }
+
+    return $tables;
   }
 }
diff --git a/modules/simpletest/drupal_web_test_case.php b/modules/simpletest/drupal_web_test_case.php
index aed66fa..f8e8133 100644
--- a/modules/simpletest/drupal_web_test_case.php
+++ b/modules/simpletest/drupal_web_test_case.php
@@ -1640,15 +1640,12 @@ class DrupalWebTestCase extends DrupalTestCase {
     file_unmanaged_delete_recursive($this->originalFileDirectory . '/simpletest/' . substr($this->databasePrefix, 10));
 
     // Remove all prefixed tables.
-    $tables = db_find_tables($this->databasePrefix . '%');
-    $connection_info = Database::getConnectionInfo('default');
-    $tables = db_find_tables($connection_info['default']['prefix']['default'] . '%');
+    $tables = db_find_tables('%');
     if (empty($tables)) {
       $this->fail('Failed to find test tables to drop.');
     }
-    $prefix_length = strlen($connection_info['default']['prefix']['default']);
     foreach ($tables as $table) {
-      if (db_drop_table(substr($table, $prefix_length))) {
+      if (db_drop_table($table)) {
         unset($tables[$table]);
       }
     }
diff --git a/modules/simpletest/tests/schema.test b/modules/simpletest/tests/schema.test
index 4199428..ab2abc9 100644
--- a/modules/simpletest/tests/schema.test
+++ b/modules/simpletest/tests/schema.test
@@ -381,4 +381,60 @@ class SchemaTestCase extends DrupalWebTestCase {
 
     db_drop_field($table_name, $field_name);
   }
+
+  /**
+   * Tests the findTables() method.
+   */
+  public function testFindTables() {
+    // We will be testing with three tables, two of them using the default
+    // prefix and the third one with an individually specified prefix.
+
+    // Set up a new connection with different connection info.
+    $connection_info = Database::getConnectionInfo();
+
+    // Add per-table prefix to the second table.
+    $new_connection_info = $connection_info['default'];
+    $new_connection_info['prefix']['test_2_table'] = $new_connection_info['prefix']['default'] . '_shared_';
+    Database::addConnectionInfo('test', 'default', $new_connection_info);
+
+    Database::setActiveConnection('test');
+
+    // Create the tables.
+    $table_specification = array(
+      'description' => 'Test table.',
+      'fields' => array(
+        'id'  => array(
+          'type' => 'int',
+          'default' => NULL,
+        ),
+      ),
+    );
+    Database::getConnection()->schema()->createTable('test_1_table', $table_specification);
+    Database::getConnection()->schema()->createTable('test_2_table', $table_specification);
+    Database::getConnection()->schema()->createTable('the_third_table', $table_specification);
+
+    // Check the "all tables" syntax.
+    $tables = Database::getConnection()->schema()->findTables('%');
+    sort($tables);
+    $expected = array(
+      'test_1_table',
+      // This table uses a per-table prefix, yet it is returned as un-prefixed.
+      'test_2_table',
+      'the_third_table',
+    );
+
+    $this->assertTrue(!array_diff($expected, $tables), 'All tables were found.');
+
+    // Check the restrictive syntax.
+    $tables = Database::getConnection()->schema()->findTables('test_%');
+    sort($tables);
+    $expected = array(
+      'test_1_table',
+      'test_2_table',
+    );
+    $this->assertEqual($tables, $expected, 'Two tables were found.');
+
+    // Go back to the initial connection.
+    Database::setActiveConnection('default');
+  }
 }
diff --git a/modules/system/system.test b/modules/system/system.test
index bc764dd..dea8d6b 100644
--- a/modules/system/system.test
+++ b/modules/system/system.test
@@ -28,7 +28,7 @@ class ModuleTestCase extends DrupalWebTestCase {
    *   specified base table. Defaults to TRUE.
    */
   function assertTableCount($base_table, $count = TRUE) {
-    $tables = db_find_tables(Database::getConnection()->prefixTables('{' . $base_table . '}') . '%');
+    $tables = db_find_tables($base_table . '%');
 
     if ($count) {
       return $this->assertTrue($tables, format_string('Tables matching "@base_table" found.', array('@base_table' => $base_table)));
