diff --git a/core/includes/lock.inc b/core/includes/lock.inc
index 7dd8db3..0a6b443 100644
--- a/core/includes/lock.inc
+++ b/core/includes/lock.inc
@@ -1,5 +1,7 @@
 <?php
 
+use Drupal\Core\Database\IntegrityConstraintViolationException;
+
 /**
  * @file
  * A database-mediated implementation of a locking mechanism.
@@ -137,7 +139,7 @@ function lock_acquire($name, $timeout = 30.0) {
         // We never need to try again.
         $retry = FALSE;
       }
-      catch (PDOException $e) {
+      catch (IntegrityConstraintViolationException $e) {
         // Suppress the error. If this is our first pass through the loop,
         // then $retry is FALSE. In this case, the insert must have failed
         // meaning some other request acquired the lock but did not release it.
diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php
index 6e9e44a..c2d75b9 100644
--- a/core/lib/Drupal/Core/Database/Connection.php
+++ b/core/lib/Drupal/Core/Database/Connection.php
@@ -479,13 +479,14 @@ abstract class Connection extends PDO {
    * @return Drupal\Core\Database\StatementInterface
    *   This method will return one of: the executed statement, the number of
    *   rows affected by the query (not the number matched), or the generated
-   *   insert IT of the last query, depending on the value of
+   *   insert ID of the last query, depending on the value of
    *   $options['return']. Typically that value will be set by default or a
    *   query builder and should not be set by a user. If there is an error,
    *   this method will return NULL and may throw an exception if
    *   $options['throw_exception'] is TRUE.
    *
    * @throws PDOException
+   * @throws Drupal\Core\Database\IntegrityConstraintViolationException
    */
   public function query($query, array $args = array(), $options = array()) {
 
@@ -524,6 +525,10 @@ abstract class Connection extends PDO {
     }
     catch (PDOException $e) {
       if ($options['throw_exception']) {
+        // Match all SQLSTATE 23xxx errors.
+        if (substr($e->getCode(), -6, -3) == '23') {
+          $e = new IntegrityConstraintViolationException($e->getMessage(), $e->getCode(), $e);
+        }
         // Add additional debug information.
         if ($query instanceof DatabaseStatementInterface) {
           $e->query_string = $stmt->getQueryString();
diff --git a/core/lib/Drupal/Core/Database/IntegrityConstraintViolationException.php b/core/lib/Drupal/Core/Database/IntegrityConstraintViolationException.php
new file mode 100644
index 0000000..7b8ac43
--- /dev/null
+++ b/core/lib/Drupal/Core/Database/IntegrityConstraintViolationException.php
@@ -0,0 +1,18 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Database\IntegrityConstraintViolationException
+ */
+
+namespace Drupal\Core\Database;
+
+use RuntimeException;
+
+/**
+ * Exception thrown if a query would violate an integrity constraint.
+ *
+ * This exception is thrown e.g. when trying to insert a row that would violate
+ * a unique key constraint.
+ */
+class IntegrityConstraintViolationException extends RuntimeException implements DatabaseException { }
diff --git a/core/lib/Drupal/Core/Database/Query/Merge.php b/core/lib/Drupal/Core/Database/Query/Merge.php
index 547a223..3d7564a 100644
--- a/core/lib/Drupal/Core/Database/Query/Merge.php
+++ b/core/lib/Drupal/Core/Database/Query/Merge.php
@@ -9,6 +9,7 @@ namespace Drupal\Core\Database\Query;
 
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\IntegrityConstraintViolationException;
 
 use Exception;
 
@@ -423,7 +424,7 @@ class Merge extends Query implements ConditionInterface {
           $insert->execute();
           return self::STATUS_INSERT;
         }
-        catch (Exception $e) {
+        catch (IntegrityConstraintViolationException $e) {
           // The insert query failed, maybe it's because a racing insert query
           // beat us in inserting the same row. Retry the select query, if it
           // returns a row, ignore the error and continue with the update
diff --git a/core/modules/simpletest/tests/database_test.test b/core/modules/simpletest/tests/database_test.test
index 487953e..d18c5aa 100644
--- a/core/modules/simpletest/tests/database_test.test
+++ b/core/modules/simpletest/tests/database_test.test
@@ -8,6 +8,7 @@ use Drupal\Core\Database\TransactionNoActiveException;
 use Drupal\Core\Database\Query\Merge;
 use Drupal\Core\Database\Query\InvalidMergeQueryException;
 use Drupal\Core\Database\Query\NoFieldsException;
+use Drupal\Core\Database\IntegrityConstraintViolationException;
 
 /**
  * Dummy class for fetching into a class.
@@ -3169,7 +3170,7 @@ class DatabaseInvalidDataTestCase extends DatabaseTestCase {
         ->execute();
       $this->fail(t('Insert succeedded when it should not have.'));
     }
-    catch (Exception $e) {
+    catch (IntegrityConstraintViolationException $e) {
       // Check if the first record was inserted.
       $name = db_query('SELECT name FROM {test} WHERE age = :age', array(':age' => 63))->fetchField();
 
