diff --git a/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php b/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php
index 5dcd487..fa90b45 100644
--- a/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php
+++ b/core/lib/Drupal/Core/Database/Driver/mysql/Schema.php
@@ -275,6 +275,16 @@ protected function createKeysSql($spec) {
     if (!empty($spec['primary key'])) {
       $keys[] = 'PRIMARY KEY (' . $this->createKeySql($spec['primary key']) . ')';
     }
+    if (!empty($spec['foreign keys'])) {
+      foreach ($spec['foreign keys'] as $key => $fields) {
+        if (!empty($fields['on update']) && !empty($fields['on delete'])) {
+          $fk = $this->createForeignKeySql($key, $fields);
+          if ($fk) {
+            $keys[] = $fk;
+          }
+        }
+      }
+    }
     if (!empty($spec['unique keys'])) {
       foreach ($spec['unique keys'] as $key => $fields) {
         $keys[] = 'UNIQUE KEY `' . $key . '` (' . $this->createKeySql($fields) . ')';
@@ -366,6 +376,19 @@ protected function createKeySql($fields) {
     return implode(', ', $return);
   }
 
+  /**
+   * @return string|null
+   *   The SQL definition of the foreign key or NULL if the
+   *   definition is invalid.
+   */
+  protected function createForeignKeySql($key_name, $fields) {
+    $operations = ['no action', 'restrict', 'cascade', 'set null', 'set default'];
+    if (isset($fields['on update']) && isset($fields['on delete']) && in_array($fields['on update'], $operations)  && in_array($fields['on delete'], $operations)) {
+      return 'CONSTRAINT `' . $key_name . '` FOREIGN KEY (`' . implode('`, `', array_keys($fields['columns'])) . '`) REFERENCES {' . $fields['table'] . '} (`' . implode('`, `', $fields['columns']) . '`) ON DELETE ' . strtoupper($fields['on delete']) . ' ON UPDATE ' . strtoupper($fields['on update']);
+    }
+    return NULL;
+  }
+
   public function renameTable($table, $new_name) {
     if (!$this->tableExists($table)) {
       throw new SchemaObjectDoesNotExistException(t("Cannot rename @table to @table_new: table @table doesn't exist.", ['@table' => $table, '@table_new' => $new_name]));
@@ -454,6 +477,11 @@ public function indexExists($table, $name) {
     return isset($row['Key_name']);
   }
 
+  public function fkExists($table, $name) {
+    $row = $this->connection->query('SELECT CONSTRAINT_NAME FROM information_schema.key_column_usage WHERE TABLE_NAME = ' . $this->connection->quote('{' . $table . '}') . ' AND CONSTRAINT_NAME = ' . $this->connection->quote($name) . ' AND REFERENCED_TABLE_NAME IS NOT NULL')->fetchAssoc();
+    return isset($row['CONSTRAINT_NAME']);
+  }
+
   public function addPrimaryKey($table, $fields) {
     if (!$this->tableExists($table)) {
       throw new SchemaObjectDoesNotExistException(t("Cannot add primary key to table @table: table doesn't exist.", ['@table' => $table]));
@@ -474,6 +502,31 @@ public function dropPrimaryKey($table) {
     return TRUE;
   }
 
+  public function addForeignKey($table, $name, $fields) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot add foreign key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
+    }
+    if ($this->fkExists($table, $name)) {
+      throw new SchemaObjectExistsException(t("Cannot add foreign key @name to table @table: foreign key already exists.", array('@table' => $table, '@name' => $name)));
+    }
+
+    $fk = $this->createForeignKeySql($name, $fields);
+    if (!$fk) {
+      throw new SchemaException(t("Cannot add foreign key @name to table @table: invalid foreign key definition.", array('@table' => $table, '@name' => $name)));
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} ADD ' . $fk);
+  }
+
+  public function dropForeignKey($table, $name) {
+    if (!$this->fkExists($table, $name)) {
+      return FALSE;
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} DROP FOREIGN KEY `' . $name . '`');
+    return TRUE;
+  }
+
   public function addUniqueKey($table, $name, $fields) {
     if (!$this->tableExists($table)) {
       throw new SchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", ['@table' => $table, '@name' => $name]));
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php
index 19efe50..a8c38e9 100644
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php
+++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Schema.php
@@ -253,6 +253,17 @@ protected function createTableSql($name, $table) {
     if (isset($table['primary key']) && is_array($table['primary key'])) {
       $sql_keys[] = 'CONSTRAINT ' . $this->ensureIdentifiersLength($name, '', 'pkey') . ' PRIMARY KEY (' . $this->createPrimaryKeySql($table['primary key']) . ')';
     }
+
+    if (isset($table['foreign keys']) && is_array($table['foreign keys'])) {
+      foreach ($table['foreign keys'] as $key_name => $key) {
+        if (!empty($key['on update']) && !empty($key['on delete'])) {
+          $fk = $this->createForeignKeySql($name, $key_name, $key);
+          if ($fk) {
+            $sql_keys[] = $fk;
+          }
+        }
+      }
+    }
     if (isset($table['unique keys']) && is_array($table['unique keys'])) {
       foreach ($table['unique keys'] as $key_name => $key) {
         $sql_keys[] = 'CONSTRAINT ' . $this->ensureIdentifiersLength($name, $key_name, 'key') . ' UNIQUE (' . implode(', ', $key) . ')';
@@ -443,6 +454,19 @@ protected function _createKeySql($fields) {
   }
 
   /**
+   * @return string|null
+   *   The SQL definition of the foreign key or NULL if the
+   *   definition is invalid.
+   */
+  protected function createForeignKeySql($name, $key_name, $fields) {
+    $operations = ['no action', 'restrict', 'cascade', 'set null', 'set default'];
+    if (isset($fields['on update']) && isset($fields['on delete']) && in_array($fields['on update'], $operations)  && in_array($fields['on delete'], $operations)) {
+      return 'CONSTRAINT "' . $this->ensureIdentifiersLength($name, $key_name, 'fkey') . '" FOREIGN KEY (' . implode(', ', array_keys($fields['columns'])) . ') REFERENCES {' . $fields['table'] . '} (' . implode(', ', $fields['columns']) . ') ON UPDATE ' . $fields['on update'] . ' ON DELETE ' . $fields['on delete'];
+    }
+    return NULL;
+  }
+
+  /**
    * Create the SQL expression for primary keys.
    *
    * Postgresql does not support key length. It does support fillfactor, but
@@ -530,7 +554,7 @@ public function dropTable($table) {
       return FALSE;
     }
 
-    $this->connection->query('DROP TABLE {' . $table . '}');
+    $this->connection->query('DROP TABLE {' . $table . '} CASCADE');
     $this->resetTableInformation($table);
     return TRUE;
   }
@@ -664,6 +688,31 @@ public function dropPrimaryKey($table) {
     return TRUE;
   }
 
+  function addForeignKey($table, $name, $fields) {
+    if (!$this->tableExists($table)) {
+      throw new SchemaObjectDoesNotExistException(t("Cannot add foreign key @name to table @table: table doesn't exist.", array('@table' => $table, '@name' => $name)));
+    }
+    if ($this->constraintExists($table, $name . '__fkey')) {
+      throw new SchemaObjectExistsException(t("Cannot add foreign key @name to table @table: foreign key already exists.", array('@table' => $table, '@name' => $name)));
+    }
+
+    $fk = $this->createForeignKeySql($table, $name, $fields);
+    if (!$fk) {
+      throw new SchemaException(t("Cannot add foreign key @name to table @table: invalid foreign key definition.", array('@table' => $table, '@name' => $name)));
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} ADD ' . $fk);
+  }
+
+  public function dropForeignKey($table, $name) {
+    if (!$this->constraintExists($table, $name . '__fkey')) {
+      return FALSE;
+    }
+
+    $this->connection->query('ALTER TABLE {' . $table . '} DROP CONSTRAINT "' . $this->ensureIdentifiersLength($table, $name, 'fkey') . '"');
+    return TRUE;
+  }
+
   function addUniqueKey($table, $name, $fields) {
     if (!$this->tableExists($table)) {
       throw new SchemaObjectDoesNotExistException(t("Cannot add unique key @name to table @table: table doesn't exist.", ['@table' => $table, '@name' => $name]));
@@ -830,6 +879,13 @@ protected function _createKeys($table, $new_keys) {
     if (isset($new_keys['primary key'])) {
       $this->addPrimaryKey($table, $new_keys['primary key']);
     }
+    if (isset($new_keys['foreign keys'])) {
+      foreach ($new_keys['foreign keys'] as $name => $fields) {
+        if (!empty($fields['on update']) && !empty($fields['on delete'])) {
+          $this->addForeignKey($table, $name, $fields);
+        }
+      }
+    }
     if (isset($new_keys['unique keys'])) {
       foreach ($new_keys['unique keys'] as $name => $fields) {
         $this->addUniqueKey($table, $name, $fields);
diff --git a/core/modules/simpletest/simpletest.module b/core/modules/simpletest/simpletest.module
index aa1088f..717373a 100644
--- a/core/modules/simpletest/simpletest.module
+++ b/core/modules/simpletest/simpletest.module
@@ -663,6 +663,14 @@ function simpletest_clean_environment() {
  * Removes prefixed tables from the database from crashed tests.
  */
 function simpletest_clean_database() {
+  // Use transactional DDL if available.
+  $txn = Database::getConnection()->startTransaction();
+  // MySQL does not have a transactional DDL so we must disable foreign keys to
+  // delete tables that have fk relationships between them.
+  $is_mysql = Database::getConnection()->databaseType() === 'mysql';
+  if ($is_mysql) {
+    Database::getConnection()->query('SET foreign_key_checks = 0');
+  }
   $tables = db_find_tables('test%');
   $count = 0;
   foreach ($tables as $table) {
@@ -673,6 +681,10 @@ function simpletest_clean_database() {
       $count++;
     }
   }
+  if ($is_mysql) {
+    Database::getConnection()->query('SET foreign_key_checks = 1');
+  }
+  unset($txn);
 
   if ($count > 0) {
     drupal_set_message(\Drupal::translation()->formatPlural($count, 'Removed 1 leftover table.', 'Removed @count leftover tables.'));
diff --git a/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php b/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php
index 346c4ce..ab38ea1 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/SchemaTest.php
@@ -144,6 +144,18 @@ function testSchema() {
     $this->assertEqual($count, 2, 'There were two rows.');
 
     // Test renaming of keys and constraints.
+
+    // Table to be target of foreign key.
+    $table_specification = array(
+      'fields' => array(
+        'id'  => array(
+          'type' => 'serial',
+        ),
+      ),
+      'primary key' => array('id'),
+    );
+    db_create_table('fk_test_table', $table_specification);
+
     db_drop_table('test_table');
     $table_specification = [
       'fields' => [
@@ -157,6 +169,20 @@ function testSchema() {
         ],
       ],
       'primary key' => ['id'],
+      'foreign keys' => [
+        'test_fk_create' => [
+          'table' => 'fk_test_table',
+          'columns' => ['test_field' => 'id'],
+          'on update' => 'cascade',
+          'on delete' => 'set null',
+        ],
+        // The following should not create anything in the database because
+        // 'on update' and 'on delete' are not set.
+        'test_fk_no_create' => [
+          'table' => 'no_table',
+          'columns' => ['test_field' => 'id'],
+        ],
+      ],
       'unique keys' => [
         'test_field' => ['test_field'],
       ],
@@ -185,6 +211,12 @@ function testSchema() {
     $this->assertIdentical($primary_key_exists, TRUE, 'Primary key created.');
     $this->assertIdentical($unique_key_exists, TRUE, 'Unique key created.');
 
+    // Test for existing foreign keys.
+    $foreign_key_exists = $this->fkExists('test_table', 'test_fk_create');
+    $this->assertIdentical(isset($foreign_key_exists) ? $foreign_key_exists : TRUE, TRUE, 'Foreign key test_fk_create created.');
+    $foreign_key_exists = $this->fkExists('test_table', 'test_fk_no_create');
+    $this->assertIdentical(isset($foreign_key_exists) ? $foreign_key_exists : FALSE, FALSE, 'Foreign key test_fk_no_create not created.');
+
     db_rename_table('test_table', 'test_table2');
 
     // Test for renamed primary and unique keys.
@@ -214,6 +246,16 @@ function testSchema() {
       $this->assertEqual($sequence_name, current($info->sequences), 'Sequence was renamed.');
     }
 
+    // Test creating foreign keys.
+    Database::getConnection()->schema()->addForeignKey('test_table2', 'test_fk_add_del', $table_specification['foreign keys']['test_fk_create']);
+    $foreign_key_exists = $this->fkExists('test_table2', 'test_fk_add_del');
+    $this->assertIdentical(isset($foreign_key_exists) ? $foreign_key_exists : TRUE, TRUE, 'Foreign key was added.');
+
+    // Test deleting foreign keys.
+    Database::getConnection()->schema()->dropForeignKey('test_table2', 'test_fk_add_del');
+    $foreign_key_exists = $this->fkExists('test_table2', 'test_fk_add_del');
+    $this->assertIdentical(isset($foreign_key_exists) ? $foreign_key_exists : FALSE, FALSE, 'Foreign key was deleted.');
+
     // Use database specific data type and ensure that table is created.
     $table_specification = [
       'description' => 'Schema table description.',
@@ -236,6 +278,30 @@ function testSchema() {
   }
 
   /**
+   * Return whether a foreign key exists.
+   *
+   * @param string $table
+   *   The name of the table.
+   * @psram string $name
+   *   The name of the key.
+   *
+   * @return bool|null
+   *   Whther or not the foreign key exists or NULL if the driver does not
+   *   support foreign keys.
+   */
+  private function fkExists($table, $name) {
+    switch (Database::getConnection()->databaseType()) {
+      case 'pgsql':
+        return Database::getConnection()->schema()->constraintExists($table, $name . '__fkey');
+      case 'sqlite':
+        // SQLite does not create foreign keys.
+        return NULL;
+      default:
+        return Database::getConnection()->schema()->fkExists($table, $name);
+    }
+  }
+
+  /**
    * Tests that indexes on string fields are limited to 191 characters on MySQL.
    *
    * @see \Drupal\Core\Database\Driver\mysql\Schema::getNormalizedIndexes()
diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php
index 5621d4a..eb3691e 100644
--- a/core/tests/Drupal/KernelTests/KernelTestBase.php
+++ b/core/tests/Drupal/KernelTests/KernelTestBase.php
@@ -691,11 +691,24 @@ protected function tearDown() {
     $test_prefix = $test_connection_info['default']['prefix']['default'];
     if ($original_prefix != $test_prefix) {
       $tables = Database::getConnection()->schema()->findTables('%');
+      // Use transactional DDL if available.
+      $txn = Database::getConnection()->startTransaction();
+      // MySQL does not have a transactional DDL so we must
+      // disable foreign keys to delete tables that have
+      // fk relationships between them.
+      $is_mysql = Database::getConnection()->databaseType() === 'mysql';
+      if ($is_mysql) {
+        Database::getConnection()->query('SET foreign_key_checks = 0');
+      }
       foreach ($tables as $table) {
         if (Database::getConnection()->schema()->dropTable($table)) {
           unset($tables[$table]);
         }
       }
+      if ($is_mysql) {
+        Database::getConnection()->query('SET foreign_key_checks = 1');
+      }
+      unset($txn);
     }
 
     // Free up memory: Own properties.
