diff --git a/core/includes/database.inc b/core/includes/database.inc
index 6891ea92e1..3b6ce37a9b 100644
--- a/core/includes/database.inc
+++ b/core/includes/database.inc
@@ -181,9 +181,14 @@ function db_insert($table, array $options = []) {
  * @see \Drupal\Core\Database\Connection::defaultOptions()
  */
 function db_merge($table, array $options = []) {
+  // @todo Move defaults to connection https://www.drupal.org/node/2947775
   if (empty($options['target']) || $options['target'] == 'replica') {
     $options['target'] = 'default';
   }
+  @trigger_error(
+    "Deprecated as of Drupal 8.0.x, will be removed in Drupal 9.0.0. Instead, get a database connection injected into your service from the container and call merge() on it. For example, \$injected_database->merge(\$table, \$options);",
+    E_USER_DEPRECATED
+  );
   return Database::getConnection($options['target'])->merge($table, $options);
 }
 
diff --git a/core/lib/Drupal/Core/Entity/entity.api.php b/core/lib/Drupal/Core/Entity/entity.api.php
index 2b893fd7b3..51f7ba5497 100644
--- a/core/lib/Drupal/Core/Entity/entity.api.php
+++ b/core/lib/Drupal/Core/Entity/entity.api.php
@@ -1245,7 +1245,7 @@ function hook_entity_predelete(Drupal\Core\Entity\EntityInterface $entity) {
     ->fetchField();
 
   // Log the count in a table that records this statistic for deleted entities.
-  db_merge('example_deleted_entity_statistics')
+  \Drupal::database()->merge('example_deleted_entity_statistics')
     ->key(['type' => $type, 'id' => $id])
     ->fields(['count' => $count])
     ->execute();
@@ -1273,7 +1273,7 @@ function hook_ENTITY_TYPE_predelete(Drupal\Core\Entity\EntityInterface $entity)
     ->fetchField();
 
   // Log the count in a table that records this statistic for deleted entities.
-  db_merge('example_deleted_entity_statistics')
+  \Drupal::database()->merge('example_deleted_entity_statistics')
     ->key(['type' => $type, 'id' => $id])
     ->fields(['count' => $count])
     ->execute();
diff --git a/core/modules/history/history.module b/core/modules/history/history.module
index f36121ef51..58259c910c 100644
--- a/core/modules/history/history.module
+++ b/core/modules/history/history.module
@@ -107,7 +107,7 @@ function history_write($nid, $account = NULL) {
   }
 
   if ($account->isAuthenticated()) {
-    db_merge('history')
+    \Drupal::database()->merge('history')
       ->keys([
         'uid' => $account->id(),
         'nid' => $nid,
diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module
index 9e424484b8..7f4c0bae50 100644
--- a/core/modules/locale/locale.module
+++ b/core/modules/locale/locale.module
@@ -837,7 +837,7 @@ function locale_translation_get_file_history() {
  *   FALSE on failure. Otherwise SAVED_NEW or SAVED_UPDATED.
  */
 function locale_translation_update_file_history($file) {
-  $status = db_merge('locale_file')
+  $status = \Drupal::database()->merge('locale_file')
     ->key([
       'project' => $file->project,
       'langcode' => $file->langcode,
diff --git a/core/modules/search/search.module b/core/modules/search/search.module
index 5be9206bc3..be665e6729 100644
--- a/core/modules/search/search.module
+++ b/core/modules/search/search.module
@@ -211,13 +211,14 @@ function search_cron() {
  * up to date (even if cron times out or otherwise fails).
  */
 function search_update_totals() {
+  $connection = \Drupal::database();
   // Update word IDF (Inverse Document Frequency) counts for new/changed words.
   foreach (search_dirty() as $word => $dummy) {
     // Get total count
     $total = db_query("SELECT SUM(score) FROM {search_index} WHERE word = :word", [':word' => $word], ['target' => 'replica'])->fetchField();
     // Apply Zipf's law to equalize the probability distribution.
     $total = log10(1 + 1 / (max(1, $total)));
-    db_merge('search_total')
+    $connection->merge('search_total')
       ->key('word', $word)
       ->fields(['count' => $total])
       ->execute();
@@ -536,6 +537,7 @@ function search_index($type, $sid, $langcode, $text) {
   // cache tags.
   search_index_clear($type, $sid, $langcode);
 
+  $connection = \Drupal::database();
   // Insert cleaned up data into dataset
   db_insert('search_dataset')
     ->fields([
@@ -552,7 +554,7 @@ function search_index($type, $sid, $langcode, $text) {
     // If a word already exists in the database, its score gets increased
     // appropriately. If not, we create a new record with the appropriate
     // starting score.
-    db_merge('search_index')
+    $connection->merge('search_index')
       ->keys([
         'word' => $word,
         'sid' => $sid,
diff --git a/core/modules/shortcut/src/ShortcutSetStorage.php b/core/modules/shortcut/src/ShortcutSetStorage.php
index b136c2fb07..86d36a2a3d 100644
--- a/core/modules/shortcut/src/ShortcutSetStorage.php
+++ b/core/modules/shortcut/src/ShortcutSetStorage.php
@@ -71,7 +71,8 @@ public function deleteAssignedShortcutSets(ShortcutSetInterface $entity) {
    * {@inheritdoc}
    */
   public function assignUser(ShortcutSetInterface $shortcut_set, $account) {
-    db_merge('shortcut_set_users')
+    // @todo Inject database service https://www.drupal.org/node/2947769
+    \Drupal::database()->merge('shortcut_set_users')
       ->key('uid', $account->id())
       ->fields(['set_name' => $shortcut_set->id()])
       ->execute();
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index be8532a2df..434a31f7ac 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -546,8 +546,9 @@ function taxonomy_build_node_index($node) {
     }
     // Insert index entries for all the node's terms.
     if (!empty($tid_all)) {
+      $connection = \Drupal::database();
       foreach ($tid_all as $tid) {
-        db_merge('taxonomy_index')
+        $connection->merge('taxonomy_index')
           ->key(['nid' => $node->id(), 'tid' => $tid, 'status' => $node->isPublished()])
           ->fields(['sticky' => $sticky, 'created' => $node->getCreatedTime()])
           ->execute();
diff --git a/core/modules/tracker/tracker.module b/core/modules/tracker/tracker.module
index bf40dc1e8d..f129ad132e 100644
--- a/core/modules/tracker/tracker.module
+++ b/core/modules/tracker/tracker.module
@@ -259,7 +259,8 @@ function _tracker_add($nid, $uid, $changed) {
   $changed = max($node->changed, $changed);
 
   // Update the node-level data.
-  db_merge('tracker_node')
+  $connection = \Drupal::database();
+  $connection->merge('tracker_node')
     ->key('nid', $nid)
     ->fields([
       'changed' => $changed,
@@ -268,7 +269,7 @@ function _tracker_add($nid, $uid, $changed) {
     ->execute();
 
   // Create or update the user-level data, first for the user posting.
-  db_merge('tracker_user')
+  $connection->merge('tracker_user')
     ->keys([
       'nid' => $nid,
       'uid' => $uid,
diff --git a/core/tests/Drupal/KernelTests/Core/Database/MergeTest.php b/core/tests/Drupal/KernelTests/Core/Database/MergeTest.php
index 9a5688c5df..10e0ac852e 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/MergeTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/MergeTest.php
@@ -18,7 +18,8 @@ class MergeTest extends DatabaseTestBase {
   public function testMergeInsert() {
     $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
 
-    $result = db_merge('test_people')
+    // @todo Inject database service https://www.drupal.org/node/2947769
+    $result = \Drupal::database()->merge('test_people')
       ->key('job', 'Presenter')
       ->fields([
         'age' => 31,
@@ -43,7 +44,7 @@ public function testMergeInsert() {
   public function testMergeUpdate() {
     $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
 
-    $result = db_merge('test_people')
+    $result = \Drupal::database()->merge('test_people')
       ->key('job', 'Speaker')
       ->fields([
         'age' => 31,
@@ -71,7 +72,7 @@ public function testMergeUpdate() {
   public function testMergeUpdateExcept() {
     $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
 
-    db_merge('test_people')
+    \Drupal::database()->merge('test_people')
       ->key('job', 'Speaker')
       ->insertFields(['age' => 31])
       ->updateFields(['name' => 'Tiffany'])
@@ -92,7 +93,7 @@ public function testMergeUpdateExcept() {
   public function testMergeUpdateExplicit() {
     $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
 
-    db_merge('test_people')
+    \Drupal::database()->merge('test_people')
       ->key('job', 'Speaker')
       ->insertFields([
         'age' => 31,
@@ -125,7 +126,7 @@ public function testMergeUpdateExpression() {
     // Note that we are also double-setting age here, once as a literal and
     // once as an expression. This test will only pass if the expression wins,
     // which is what is supposed to happen.
-    db_merge('test_people')
+    \Drupal::database()->merge('test_people')
       ->key('job', 'Speaker')
       ->fields(['name' => 'Tiffany'])
       ->insertFields(['age' => 31])
@@ -147,7 +148,7 @@ public function testMergeUpdateExpression() {
   public function testMergeInsertWithoutUpdate() {
     $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
 
-    db_merge('test_people')
+    \Drupal::database()->merge('test_people')
       ->key('job', 'Presenter')
       ->execute();
 
@@ -166,7 +167,7 @@ public function testMergeInsertWithoutUpdate() {
   public function testMergeUpdateWithoutUpdate() {
     $num_records_before = db_query('SELECT COUNT(*) FROM {test_people}')->fetchField();
 
-    db_merge('test_people')
+    \Drupal::database()->merge('test_people')
       ->key('job', 'Speaker')
       ->execute();
 
@@ -178,7 +179,7 @@ public function testMergeUpdateWithoutUpdate() {
     $this->assertEqual($person->age, 30, 'Age skipped correctly.');
     $this->assertEqual($person->job, 'Speaker', 'Job skipped correctly.');
 
-    db_merge('test_people')
+    \Drupal::database()->merge('test_people')
       ->key('job', 'Speaker')
       ->insertFields(['age' => 31])
       ->execute();
@@ -201,7 +202,7 @@ public function testInvalidMerge() {
       // Normally it would throw an exception but we are suppressing it with
       // the throw_exception option.
       $options['throw_exception'] = FALSE;
-      db_merge('test_people', $options)
+      \Drupal::database()->merge('test_people', $options)
         ->fields([
           'age' => 31,
           'name' => 'Tiffany',
@@ -216,7 +217,7 @@ public function testInvalidMerge() {
 
     try {
       // This query will fail because there is no key field specified.
-      db_merge('test_people')
+      \Drupal::database()->merge('test_people')
         ->fields([
           'age' => 31,
           'name' => 'Tiffany',
diff --git a/core/tests/Drupal/KernelTests/Core/Database/RegressionTest.php b/core/tests/Drupal/KernelTests/Core/Database/RegressionTest.php
index 22173dae66..d5e50c591c 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/RegressionTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/RegressionTest.php
@@ -3,11 +3,13 @@
 namespace Drupal\KernelTests\Core\Database;
 
 use Drupal\Core\Database\Database;
+use Drupal\Core\Database\Query\Merge;
 
 /**
  * Regression tests cases for the database layer.
  *
  * @group Database
+ * @group legacy
  */
 class RegressionTest extends DatabaseTestBase {
 
@@ -59,6 +61,15 @@ public function testDBIndexExists() {
     $this->assertSame(FALSE, db_index_exists('test', 'nosuchindex'), 'Returns false for nonexistent index.');
   }
 
+  /**
+   * Tests the db_merge() function is deprecated.
+   *
+   * @expectedDeprecation Deprecated as of Drupal 8.0.x, will be removed in Drupal 9.0.0. Instead, get a database connection injected into your service from the container and call merge() on it. For example, $injected_database->merge($table, $options);
+   */
+  public function testDBMergeIsDeprecated() {
+    $this->assertTrue(db_merge('test') instanceof Merge);
+  }
+
   /**
    * Tests the db_set_active() function.
    *
diff --git a/core/tests/Drupal/KernelTests/Core/KeyValueStore/GarbageCollectionTest.php b/core/tests/Drupal/KernelTests/Core/KeyValueStore/GarbageCollectionTest.php
index ea76f0a4f5..e0107fc143 100644
--- a/core/tests/Drupal/KernelTests/Core/KeyValueStore/GarbageCollectionTest.php
+++ b/core/tests/Drupal/KernelTests/Core/KeyValueStore/GarbageCollectionTest.php
@@ -41,9 +41,10 @@ public function testGarbageCollection() {
     }
     $this->assertIdentical(count($store->getAll()), 4, 'Four items were written to the storage.');
 
+    $connection = \Drupal::database();
     // Manually expire the data.
     for ($i = 0; $i <= 3; $i++) {
-      db_merge('key_value_expire')
+      $connection->merge('key_value_expire')
         ->keys([
             'name' => 'key_' . $i,
             'collection' => $collection,
