diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php
index 94a23fb..f377100 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()) {
 
@@ -529,7 +530,13 @@ abstract class Connection extends PDO {
         // debug information.
         $query_string = ($query instanceof DatabaseStatementInterface) ? $stmt->getQueryString() : $query;
         $message = $e->getMessage() . ": " . $query_string . "; " . print_r($args, TRUE);
-        $exception = new DatabaseExceptionWrapper($message, 0, $e);
+        // Match all SQLSTATE 23xxx errors.
+        if (substr($e->getCode(), -6, -3) == '23') {
+          $exception = new IntegrityConstraintViolationException($message, $e->getCode(), $e);
+        }
+        else {
+          $exception = new DatabaseExceptionWrapper($message, 0, $e);
+        }
 
         throw $exception;
       }
diff --git a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
index b3f6d64..ee22c9b 100644
--- a/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
+++ b/core/lib/Drupal/Core/Database/Driver/pgsql/Connection.php
@@ -10,6 +10,7 @@ namespace Drupal\Core\Database\Driver\pgsql;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Database\Connection as DatabaseConnection;
 use Drupal\Core\Database\StatementInterface;
+use Drupal\Core\Database\IntegrityConstraintViolationException;
 
 use PDO;
 use PDOException;
@@ -124,6 +125,10 @@ class Connection extends DatabaseConnection {
     }
     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 StatementInterface) {
           $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/lib/Drupal/Core/Lock/DatabaseLockBackend.php b/core/lib/Drupal/Core/Lock/DatabaseLockBackend.php
index d877dba..b0ee0a3 100644
--- a/core/lib/Drupal/Core/Lock/DatabaseLockBackend.php
+++ b/core/lib/Drupal/Core/Lock/DatabaseLockBackend.php
@@ -7,7 +7,7 @@
 
 namespace Drupal\Core\Lock;
 
-use Drupal\Core\Database\DatabaseExceptionWrapper;
+use Drupal\Core\Database\IntegrityConstraintViolationException;
 
 /**
  * Defines the database lock backend. This is the default backend in Drupal.
@@ -53,12 +53,12 @@ class DatabaseLockBackend extends LockBackendAbstract {
           // We never need to try again.
           $retry = FALSE;
         }
-        catch (DatabaseExceptionWrapper $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.
-          // We decide whether to retry by checking lock_may_be_available()
-          // Since this will break the lock in case it is expired.
+          // then $retry is FALSE. In this case, the insert failed because some
+          // other request acquired the lock but did not release it. We decide
+          // whether to retry by checking lockMayBeAvailable(). This will clear
+          // the offending row from the database table in case it has expired.
           $retry = $retry ? FALSE : $this->lockMayBeAvailable($name);
         }
         // We only retry in case the first attempt failed, but we then broke
diff --git a/core/modules/system/tests/database.test b/core/modules/system/tests/database.test
index ef0807b..a9b70e5 100644
--- a/core/modules/system/tests/database.test
+++ b/core/modules/system/tests/database.test
@@ -1,6 +1,7 @@
 <?php
 
 use Drupal\Core\Database\Database;
+use Drupal\Core\Database\IntegrityConstraintViolationException;
 use Drupal\Core\Database\StatementEmpty;
 use Drupal\Core\Database\StatementInterface;
 use Drupal\Core\Database\TransactionOutOfOrderException;
@@ -3209,7 +3210,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();
 
