diff --git a/core/core.services.yml b/core/core.services.yml
index 245ffa721e..4d9bd3218e 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -355,6 +355,8 @@ services:
     class: Drupal\Core\Database\Connection
     factory: Drupal\Core\Database\Database::getConnection
     arguments: [default]
+  database.factory:
+    class: Drupal\Core\Database\DatabaseFactory
   datetime.time:
     class: Drupal\Component\Datetime\Time
     arguments: ['@request_stack']
diff --git a/core/includes/database.inc b/core/includes/database.inc
index 7e2fe41ef1..b638a05cb5 100644
--- a/core/includes/database.inc
+++ b/core/includes/database.inc
@@ -54,11 +54,9 @@
  * @see \Drupal\Core\Database\Connection::defaultOptions()
  */
 function db_query($query, array $args = [], array $options = []) {
-  if (empty($options['target'])) {
-    $options['target'] = 'default';
-  }
-
-  return Database::getConnection($options['target'])->query($query, $args, $options);
+  $target = empty($options['target']) ? 'default' : $options['target'];
+  unset($options['target']);
+  return Database::getConnection($target)->query($query, $args, $options);
 }
 
 /**
@@ -93,11 +91,9 @@ function db_query($query, array $args = [], array $options = []) {
  */
 function db_query_range($query, $from, $count, array $args = [], array $options = []) {
   @trigger_error('db_query_range() is deprecated in Drupal 8.0.x and will be removed before Drupal 9.0.0. Instead, get a database connection injected into your service from the container and call queryRange() on it. For example, $injected_database->queryRange($query, $from, $count, $args, $options). See https://www.drupal.org/node/2993033', E_USER_DEPRECATED);
-  if (empty($options['target'])) {
-    $options['target'] = 'default';
-  }
-
-  return Database::getConnection($options['target'])->queryRange($query, $from, $count, $args, $options);
+  $target = empty($options['target']) ? 'default' : $options['target'];
+  unset($options['target']);
+  return Database::getConnection($target)->queryRange($query, $from, $count, $args, $options);
 }
 
 /**
@@ -130,11 +126,9 @@ function db_query_range($query, $from, $count, array $args = [], array $options
  */
 function db_query_temporary($query, array $args = [], array $options = []) {
   @trigger_error('db_query_temporary() is deprecated in Drupal 8.0.x and will be removed before Drupal 9.0.0. Instead, get a database connection injected into your service from the container and call queryTemporary() on it. For example, $injected_database->queryTemporary($query, $args, $options). See https://www.drupal.org/node/2993033', E_USER_DEPRECATED);
-  if (empty($options['target'])) {
-    $options['target'] = 'default';
-  }
-
-  return Database::getConnection($options['target'])->queryTemporary($query, $args, $options);
+  $target = empty($options['target']) ? 'default' : $options['target'];
+  unset($options['target']);
+  return Database::getConnection($target)->queryTemporary($query, $args, $options);
 }
 
 /**
@@ -158,10 +152,9 @@ function db_query_temporary($query, array $args = [], array $options = []) {
  */
 function db_insert($table, array $options = []) {
   @trigger_error('db_insert() is deprecated in Drupal 8.0.x and will be removed before Drupal 9.0.0. Instead, get a database connection injected into your service from the container and call insert() on it. For example, $injected_database->insert($table, $options); See https://www.drupal.org/node/2993033', E_USER_DEPRECATED);
-  if (empty($options['target']) || $options['target'] == 'replica') {
-    $options['target'] = 'default';
-  }
-  return Database::getConnection($options['target'])->insert($table, $options);
+  $target = empty($options['target']) || $options['target'] == 'replica' ? 'default' : $options['target'];
+  unset($options['target']);
+  return Database::getConnection($target)->insert($table, $options);
 }
 
 /**
@@ -185,12 +178,9 @@ function db_insert($table, array $options = []) {
  */
 function db_merge($table, array $options = []) {
   @trigger_error('db_merge() is deprecated in Drupal 8.0.x and will be removed before 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). See https://www.drupal.org/node/2993033', E_USER_DEPRECATED);
-  // @todo Move away from here setting default target connection,
-  // https://www.drupal.org/node/2947775
-  if (empty($options['target']) || $options['target'] == 'replica') {
-    $options['target'] = 'default';
-  }
-  return Database::getConnection($options['target'])->merge($table, $options);
+  $target = empty($options['target']) || $options['target'] == 'replica' ? 'default' : $options['target'];
+  unset($options['target']);
+  return Database::getConnection($target)->merge($table, $options);
 }
 
 /**
@@ -214,10 +204,9 @@ function db_merge($table, array $options = []) {
  */
 function db_update($table, array $options = []) {
   @trigger_error('db_update() is deprecated in Drupal 8.0.x and will be removed before Drupal 9.0.0. Instead, get a database connection injected into your service from the container and call call update() on it. For example, $injected_database->update($table, $options); See https://www.drupal.org/node/2993033', E_USER_DEPRECATED);
-  if (empty($options['target']) || $options['target'] == 'replica') {
-    $options['target'] = 'default';
-  }
-  return Database::getConnection($options['target'])->update($table, $options);
+  $target = empty($options['target']) || $options['target'] == 'replica' ? 'default' : $options['target'];
+  unset($options['target']);
+  return Database::getConnection($target)->update($table, $options);
 }
 
 /**
@@ -241,12 +230,9 @@ function db_update($table, array $options = []) {
  */
 function db_delete($table, array $options = []) {
   @trigger_error('db_delete is deprecated in Drupal 8.0.x and will be removed before Drupal 9.0.0. Instead, get a database connection injected into your service from the container and call delete() on it. For example, $injected_database->delete($table, $options). See https://www.drupal.org/node/2993033', E_USER_DEPRECATED);
-  // @todo Move away from here setting default target connection,
-  // https://www.drupal.org/node/2947775
-  if (empty($options['target']) || $options['target'] == 'replica') {
-    $options['target'] = 'default';
-  }
-  return Database::getConnection($options['target'])->delete($table, $options);
+  $target = empty($options['target']) || $options['target'] == 'replica' ? 'default' : $options['target'];
+  unset($options['target']);
+  return Database::getConnection($target)->delete($table, $options);
 }
 
 /**
@@ -270,10 +256,9 @@ function db_delete($table, array $options = []) {
  */
 function db_truncate($table, array $options = []) {
   @trigger_error('db_truncate() is deprecated in Drupal 8.0.x and will be removed before Drupal 9.0.0. Instead, get a database connection injected into your service from the container and call truncate() on it. For example, $injected_database->truncate($table, $options). See https://www.drupal.org/node/2993033', E_USER_DEPRECATED);
-  if (empty($options['target']) || $options['target'] == 'replica') {
-    $options['target'] = 'default';
-  }
-  return Database::getConnection($options['target'])->truncate($table, $options);
+  $target = empty($options['target']) || $options['target'] == 'replica' ? 'default' : $options['target'];
+  unset($options['target']);
+  return Database::getConnection($target)->truncate($table, $options);
 }
 
 /**
@@ -300,10 +285,9 @@ function db_truncate($table, array $options = []) {
  * @see \Drupal\Core\Database\Connection::defaultOptions()
  */
 function db_select($table, $alias = NULL, array $options = []) {
-  if (empty($options['target'])) {
-    $options['target'] = 'default';
-  }
-  return Database::getConnection($options['target'])->select($table, $alias, $options);
+  $target = empty($options['target']) ? 'default' : $options['target'];
+  unset($options['target']);
+  return Database::getConnection($target)->select($table, $alias, $options);
 }
 
 /**
@@ -328,12 +312,9 @@ function db_select($table, $alias = NULL, array $options = []) {
  */
 function db_transaction($name = NULL, array $options = []) {
   @trigger_error('db_transaction is deprecated in Drupal 8.0.x and will be removed before Drupal 9.0.0. Instead, get a database connection injected into your service from the container and call startTransaction() on it. For example, $injected_database->startTransaction($name). See https://www.drupal.org/node/2993033', E_USER_DEPRECATED);
-  // @todo Move away from here setting default target connection,
-  // https://www.drupal.org/node/2947775
-  if (empty($options['target'])) {
-    $options['target'] = 'default';
-  }
-  return Database::getConnection($options['target'])->startTransaction($name);
+  $target = empty($options['target']) ? 'default' : $options['target'];
+  unset($options['target']);
+  return Database::getConnection($target)->startTransaction($name);
 }
 
 /**
@@ -472,10 +453,9 @@ function db_driver() {
  */
 function db_close(array $options = []) {
   @trigger_error('db_close() is deprecated in Drupal 8.0.x and will be removed before Drupal 9.0.0. Use \Drupal\Core\Database\Database::closeConnection() instead. See https://www.drupal.org/node/2993033.', E_USER_DEPRECATED);
-  if (empty($options['target'])) {
-    $options['target'] = NULL;
-  }
-  Database::closeConnection($options['target']);
+  $target = empty($options['target']) ? 'default' : $options['target'];
+  unset($options['target']);
+  Database::closeConnection($target);
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Database/Connection.php b/core/lib/Drupal/Core/Database/Connection.php
index eaba227db2..31d4477d60 100644
--- a/core/lib/Drupal/Core/Database/Connection.php
+++ b/core/lib/Drupal/Core/Database/Connection.php
@@ -212,12 +212,6 @@ public function destroy() {
    *
    * A given query can be customized with a number of option flags in an
    * associative array:
-   * - target: The database "target" against which to execute a query. Valid
-   *   values are "default" or "replica". The system will first try to open a
-   *   connection to a database specified with the user-supplied key. If one
-   *   is not available, it will silently fall back to the "default" target.
-   *   If multiple databases connections are specified with the same target,
-   *   one will be selected at random for the duration of the request.
    * - fetch: This element controls how rows from a result set will be
    *   returned. Legal values include PDO::FETCH_ASSOC, PDO::FETCH_BOTH,
    *   PDO::FETCH_OBJ, PDO::FETCH_NUM, or a string representing the name of a
@@ -260,7 +254,6 @@ public function destroy() {
    */
   protected function defaultOptions() {
     return [
-      'target' => 'default',
       'fetch' => \PDO::FETCH_OBJ,
       'return' => Database::RETURN_STATEMENT,
       'throw_exception' => TRUE,
@@ -600,6 +593,9 @@ protected function filterComment($comment = '') {
   public function query($query, array $args = [], $options = []) {
     // Use default values if not already set.
     $options += $this->defaultOptions();
+    if (isset($options['target'])) {
+      @trigger_error('Passing a \'target\' key to Connection::query $options argument is deprecated in Drupal 8.0.x and will be removed before Drupal 9.0.0. Instead, get the target connection via Database::getConnection() passing the target in input and execute query() on it. See https://www.drupal.org/node/3000931', E_USER_DEPRECATED);
+    }
 
     try {
       // We allow either a pre-bound statement object or a literal string.
diff --git a/core/lib/Drupal/Core/Database/DatabaseFactory.php b/core/lib/Drupal/Core/Database/DatabaseFactory.php
new file mode 100644
index 0000000000..55da6ca096
--- /dev/null
+++ b/core/lib/Drupal/Core/Database/DatabaseFactory.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Drupal\Core\Database;
+
+/**
+ * Test database factory.
+ */
+class DatabaseFactory extends Database {
+
+  /**
+   * Gets the connection object for the specified database key and target.
+   *
+   * @param string $target
+   *   The database target name.
+   * @param null|string $key
+   *   The database connection key. Defaults to NULL which means the active key.
+   *
+   * @return \Drupal\Core\Database\Connection
+   *   Database connection instance.
+   */
+  public function getDatabaseConnection($target = 'default', $key = NULL) {
+    return self::getConnection($target, $key);
+  }
+
+}
diff --git a/core/modules/comment/src/CommentStatistics.php b/core/modules/comment/src/CommentStatistics.php
index b58452c594..f6698da706 100644
--- a/core/modules/comment/src/CommentStatistics.php
+++ b/core/modules/comment/src/CommentStatistics.php
@@ -3,6 +3,7 @@
 namespace Drupal\comment;
 
 use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\DatabaseFactory;
 use Drupal\Core\Entity\FieldableEntityInterface;
 use Drupal\Core\Entity\EntityChangedInterface;
 use Drupal\Core\Entity\EntityInterface;
@@ -41,6 +42,20 @@ class CommentStatistics implements CommentStatisticsInterface {
    */
   protected $state;
 
+  /**
+   * Connection factory.
+   *
+   * @var \Drupal\Core\Database\DatabaseFactory
+   */
+  protected $connectionFactory;
+
+  /**
+   * A replica database connection.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $replicaDatabase;
+
   /**
    * Constructs the CommentStatistics service.
    *
@@ -52,20 +67,24 @@ class CommentStatistics implements CommentStatisticsInterface {
    *   The entity manager service.
    * @param \Drupal\Core\State\StateInterface $state
    *   The state service.
+   * @param \Drupal\Core\Database\DatabaseFactory $connection_factory
+   *   Connection factory.
    */
-  public function __construct(Connection $database, AccountInterface $current_user, EntityManagerInterface $entity_manager, StateInterface $state) {
+  public function __construct(Connection $database, AccountInterface $current_user, EntityManagerInterface $entity_manager, StateInterface $state, DatabaseFactory $connection_factory) {
+    $this->connectionFactory = $connection_factory;
     $this->database = $database;
     $this->currentUser = $current_user;
     $this->entityManager = $entity_manager;
     $this->state = $state;
+    $this->replicaDatabase = $this->connectionFactory->getDatabaseConnection('replica');
   }
 
   /**
    * {@inheritdoc}
    */
   public function read($entities, $entity_type, $accurate = TRUE) {
-    $options = $accurate ? [] : ['target' => 'replica'];
-    $stats = $this->database->select('comment_entity_statistics', 'ces', $options)
+    $connection = $accurate ? $this->database : $this->replicaDatabase;
+    $stats = $connection->select('comment_entity_statistics', 'ces')
       ->fields('ces')
       ->condition('ces.entity_id', array_keys($entities), 'IN')
       ->condition('ces.entity_type', $entity_type)
diff --git a/core/modules/node/src/Plugin/Search/NodeSearch.php b/core/modules/node/src/Plugin/Search/NodeSearch.php
index b043d63681..c347883007 100644
--- a/core/modules/node/src/Plugin/Search/NodeSearch.php
+++ b/core/modules/node/src/Plugin/Search/NodeSearch.php
@@ -6,6 +6,7 @@
 use Drupal\Core\Cache\CacheableMetadata;
 use Drupal\Core\Config\Config;
 use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\DatabaseFactory;
 use Drupal\Core\Database\Query\SelectExtender;
 use Drupal\Core\Database\StatementInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
@@ -122,6 +123,20 @@ class NodeSearch extends ConfigurableSearchPluginBase implements AccessibleInter
    */
   protected $messenger;
 
+  /**
+   * Connection factory.
+   *
+   * @var \Drupal\Core\Database\DatabaseFactory
+   */
+  protected $connectionFactory;
+
+  /**
+   * A replica database connection object.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $replicaDatabase;
+
   /**
    * {@inheritdoc}
    */
@@ -137,7 +152,8 @@ public static function create(ContainerInterface $container, array $configuratio
       $container->get('language_manager'),
       $container->get('renderer'),
       $container->get('messenger'),
-      $container->get('current_user')
+      $container->get('current_user'),
+      $container->get('database.factory')
     );
   }
 
@@ -167,7 +183,8 @@ public static function create(ContainerInterface $container, array $configuratio
    * @param \Drupal\Core\Session\AccountInterface $account
    *   The $account object to use for checking for access to advanced search.
    */
-  public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, Config $search_settings, LanguageManagerInterface $language_manager, RendererInterface $renderer, MessengerInterface $messenger, AccountInterface $account = NULL) {
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, Connection $database, EntityManagerInterface $entity_manager, ModuleHandlerInterface $module_handler, Config $search_settings, LanguageManagerInterface $language_manager, RendererInterface $renderer, MessengerInterface $messenger, AccountInterface $account = NULL, DatabaseFactory $connection_factory) {
+    $this->connectionFactory = $connection_factory;
     $this->database = $database;
     $this->entityManager = $entity_manager;
     $this->moduleHandler = $module_handler;
@@ -176,6 +193,7 @@ public function __construct(array $configuration, $plugin_id, $plugin_definition
     $this->renderer = $renderer;
     $this->messenger = $messenger;
     $this->account = $account;
+    $this->replicaDatabase = $this->connectionFactory->getDatabaseConnection('replica');
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
     $this->addCacheTags(['node_list']);
@@ -236,8 +254,8 @@ protected function findResults() {
     $keys = $this->keywords;
 
     // Build matching conditions.
-    $query = $this->database
-      ->select('search_index', 'i', ['target' => 'replica'])
+    $query = $this->replicaDatabase
+      ->select('search_index', 'i')
       ->extend('Drupal\search\SearchQuery')
       ->extend('Drupal\Core\Database\Query\PagerSelectExtender');
     $query->join('node_field_data', 'n', 'n.nid = i.sid AND n.langcode = i.langcode');
diff --git a/core/tests/Drupal/KernelTests/Core/Database/DatabaseLegacyTest.php b/core/tests/Drupal/KernelTests/Core/Database/DatabaseLegacyTest.php
index 1724f1475d..0648bb19f4 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/DatabaseLegacyTest.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/DatabaseLegacyTest.php
@@ -427,6 +427,15 @@ public function testDbTruncate() {
     $this->assertInstanceOf(Truncate::class, db_truncate('test'));
   }
 
+  /**
+   * Tests deprecation of the $options 'target' key in Connection::query.
+   *
+   * @expectedDeprecation Passing a 'target' key to Connection::query $options argument is deprecated in Drupal 8.0.x and will be removed before Drupal 9.0.0. Instead, get the target connection via Database::getConnection() passing the target in input and execute query() on it. See https://www.drupal.org/node/3000931
+   */
+  public function testDbOptionsTarget() {
+    $this->assertNotNull($this->connection->query('SELECT * FROM {test}', [], ['target' => 'bar']));
+  }
+
   /**
    * Tests deprecation of the db_query_temporary() function.
    *
diff --git a/core/tests/Drupal/KernelTests/Core/Database/DatabaseTestBase.php b/core/tests/Drupal/KernelTests/Core/Database/DatabaseTestBase.php
index 784bfd6189..ce547b3541 100644
--- a/core/tests/Drupal/KernelTests/Core/Database/DatabaseTestBase.php
+++ b/core/tests/Drupal/KernelTests/Core/Database/DatabaseTestBase.php
@@ -22,9 +22,24 @@
    */
   protected $connection;
 
+  /**
+   * The replica database connection for testing.
+   *
+   * @var \Drupal\Core\Database\Connection
+   */
+  protected $replicaConnection;
+
+  /**
+   * The database connection factory.
+   *
+   * @var \Drupal\Core\Database\DatabaseFactory
+   */
+  protected $connectionFactory;
+
   protected function setUp() {
     parent::setUp();
-    $this->connection = Database::getConnection();
+    $this->connectionFactory = $this->container->get('database.factory');
+    $this->connection = $this->connectionFactory->getDatabaseConnection();
     $this->installSchema('database_test', [
       'test',
       'test_people',
