Administrar conexiones de bases de datos

Esta página proporciona las mejores prácticas y ejemplos de código específicos del lenguaje para ayudarlo a crear aplicaciones que utilicen conexiones de base de datos de Cloud SQL de manera eficaz.

Estos ejemplos son extractos de una aplicación web completa disponible en GitHub. Más información .

Para obtener instrucciones paso a paso sobre cómo ejecutar una aplicación web de muestra conectada a Cloud SQL, siga el enlace correspondiente a su entorno:

Grupos de conexiones

Un pool de conexiones es un caché de conexiones de base de datos que se comparten y reutilizan para mejorar la latencia y el rendimiento de la conexión. Cuando la aplicación necesita una conexión de base de datos, toma prestada una de su pool temporalmente; al finalizar la conexión, la devuelve al pool, donde puede reutilizarse la próxima vez que la aplicación necesite una conexión de base de datos.

Abrir y cerrar conexiones

Al usar un pool de conexiones, debe abrir y cerrar las conexiones correctamente para que siempre se devuelvan al pool al terminar de usarlas. Las conexiones no devueltas o filtradas no se reutilizan, lo que desperdicia recursos y puede causar cuellos de botella en el rendimiento de la aplicación.

Pitón

# Preparing a statement before hand can help protect against injections.
stmt = sqlalchemy.text(
    "INSERT INTO votes (time_cast, candidate) VALUES (:time_cast, :candidate)"
)
try:
    # Using a with statement ensures that the connection is always released
    # back into the pool at the end of statement (even if an error occurs)
    with db.connect() as conn:
        conn.execute(stmt, parameters={"time_cast": time_cast, "candidate": team})
        conn.commit()
except Exception as e:
    # If something goes wrong, handle the error in this section. This might
    # involve retrying or adjusting parameters depending on the situation.
    # ...

Java

// Using a try-with-resources statement ensures that the connection is always released back
// into the pool at the end of the statement (even if an error occurs)
try (Connection conn = pool.getConnection()) {

  // PreparedStatements can be more efficient and project against injections.
  String stmt = "INSERT INTO votes (time_cast, candidate) VALUES (?, ?);";
  try (PreparedStatement voteStmt = conn.prepareStatement(stmt);) {
    voteStmt.setTimestamp(1, now);
    voteStmt.setString(2, team);

    // Finally, execute the statement. If it fails, an error will be thrown.
    voteStmt.execute();
  }
} catch (SQLException ex) {
  // If something goes wrong, handle the error in this section. This might involve retrying or
  // adjusting parameters depending on the situation.
  // ...
}

Node.js

/**
 * Insert a vote record into the database.
 *
 * @param {object} pool The Knex connection object.
 * @param {object} vote The vote record to insert.
 * @returns {Promise}
 */
const insertVote = async (pool, vote) => {
  try {
    return await pool('votes').insert(vote);
  } catch (err) {
    throw Error(err);
  }
};

DO#

using Npgsql;
using System;

namespace CloudSql
{
    public class PostgreSqlTcp
    {
        public static NpgsqlConnectionStringBuilder NewPostgreSqlTCPConnectionString()
        {
            // Equivalent connection string:
            // "Uid=<DB_USER>;Pwd=<DB_PASS>;Host=<INSTANCE_HOST>;Database=<DB_NAME>;"
            var connectionString = new NpgsqlConnectionStringBuilder()
            {
                // Note: Saving credentials in environment variables is convenient, but not
                // secure - consider a more secure solution such as
                // Cloud Secret Manager (https://cloud.google.com/secret-manager) to help
                // keep secrets safe.
                Host = Environment.GetEnvironmentVariable("INSTANCE_HOST"),     // e.g. '127.0.0.1'
                // Set Host to 'cloudsql' when deploying to App Engine Flexible environment
                Username = Environment.GetEnvironmentVariable("DB_USER"), // e.g. 'my-db-user'
                Password = Environment.GetEnvironmentVariable("DB_PASS"), // e.g. 'my-db-password'
                Database = Environment.GetEnvironmentVariable("DB_NAME"), // e.g. 'my-database'

                // The Cloud SQL proxy provides encryption between the proxy and instance.
                SslMode = SslMode.Disable,
            };
            connectionString.Pooling = true;
            // Specify additional properties here.
            return connectionString;
        }
    }
}

Ir

insertVote := "INSERT INTO votes(candidate, created_at) VALUES($1, NOW())"
_, err := db.Exec(insertVote, team)

Rubí

@vote = Vote.new candidate: candidate

# ActiveRecord creates and executes your SQL and automatically
# handles the opening and closing of the database connection.
if @vote.save
  render json: "Vote successfully cast for \"#{@vote.candidate}\" at #{@vote.time_cast} PST!"
else
  render json: @vote.errors, status: :unprocessable_entity
end

PHP

// Use prepared statements to guard against SQL injection.
$sql = 'INSERT INTO votes (time_cast, candidate) VALUES (NOW(), :voteValue)';

try {
    $statement = $conn->prepare($sql);
    $statement->bindParam('voteValue', $value);

    $res = $statement->execute();
} catch (PDOException $e) {
    throw new RuntimeException(
        'Could not insert vote into database. The PDO exception was ' .
        $e->getMessage(),
        $e->getCode(),
        $e
    );
}

Recuento de conexiones

Cada conexión a la base de datos utiliza recursos del cliente y del servidor. Además, Cloud SQL impone límites de conexión generales que no se pueden superar. Crear y usar menos conexiones reduce la sobrecarga y le ayuda a mantenerse por debajo del límite de conexión.

Pitón

# Pool size is the maximum number of permanent connections to keep.
pool_size=5,
# Temporarily exceeds the set pool_size if no connections are available.
max_overflow=2,
# The total number of concurrent connections for your application will be
# a total of pool_size and max_overflow.

Java

// maximumPoolSize limits the total number of concurrent connections this pool will keep. Ideal
// values for this setting are highly variable on app design, infrastructure, and database.
config.setMaximumPoolSize(5);
// minimumIdle is the minimum number of idle connections Hikari maintains in the pool.
// Additional connections will be established to meet this value unless the pool is full.
config.setMinimumIdle(5);

Node.js

// 'max' limits the total number of concurrent connections this pool will keep. Ideal
// values for this setting are highly variable on app design, infrastructure, and database.
config.pool.max = 5;
// 'min' is the minimum number of idle connections Knex maintains in the pool.
// Additional connections will be established to meet this value unless the pool is full.
config.pool.min = 5;

DO#

// MaxPoolSize sets maximum number of connections allowed in the pool.
connectionString.MaxPoolSize = 5;
// MinPoolSize sets the minimum number of connections in the pool.
connectionString.MinPoolSize = 0;

Ir

// Set maximum number of connections in idle connection pool.
db.SetMaxIdleConns(5)

// Set maximum number of open connections to the database.
db.SetMaxOpenConns(7)

Rubí

# 'pool' is the maximum number of permanent connections to keep.
pool: 5

PHP

Actualmente, PDO no ofrece ninguna funcionalidad para configurar límites de conexión.

Retroceso exponencial

Si su aplicación intenta conectarse a la base de datos sin éxito, esta podría quedar temporalmente indisponible. En este caso, enviar solicitudes de conexión repetidas desperdicia recursos. Es preferible esperar antes de enviar solicitudes de conexión adicionales para que la base de datos vuelva a estar accesible. El uso de un retardo exponencial u otro mecanismo de retardo permite lograr este objetivo.

Este reintento solo tiene sentido al conectar por primera vez o al obtener una conexión del pool. Si se producen errores durante una transacción, la aplicación debe reintentar desde el principio. Por lo tanto, incluso si el pool está configurado correctamente, la aplicación podría seguir detectando errores si se pierden las conexiones.

Pitón

# SQLAlchemy automatically uses delays between failed connection attempts,
# but provides no arguments for configuration.

Java

// Hikari automatically delays between failed connection attempts, eventually reaching a
// maximum delay of `connectionTimeout / 2` between attempts.

Node.js

// 'knex' uses a built-in retry strategy which does not implement backoff.
// 'createRetryIntervalMillis' is how long to idle after failed connection creation before trying again
config.pool.createRetryIntervalMillis = 200; // 0.2 seconds

DO#

Policy
    .Handle<NpgsqlException>()
    .WaitAndRetry(new[]
    {
        TimeSpan.FromSeconds(1),
        TimeSpan.FromSeconds(2),
        TimeSpan.FromSeconds(5)
    })
    .Execute(() => connection.Open());

Ir

El paquete database/sql actualmente no ofrece ninguna funcionalidad para configurar el retroceso exponencial.

Rubí

# ActiveRecord automatically uses delays between failed connection attempts,
# but provides no arguments for configuration.

PHP

Actualmente, PDO no ofrece ninguna funcionalidad para configurar el retroceso exponencial.

Tiempo de espera de conexión

Hay muchas razones por las que un intento de conexión podría no tener éxito. La comunicación de red nunca está garantizada y la base de datos podría no responder temporalmente. Asegúrese de que su aplicación gestione correctamente las conexiones interrumpidas o fallidas.

Pitón

# 'pool_timeout' is the maximum number of seconds to wait when retrieving a
# new connection from the pool. After the specified amount of time, an
# exception will be thrown.
pool_timeout=30,  # 30 seconds

Java

// setConnectionTimeout is the maximum number of milliseconds to wait for a connection checkout.
// Any attempt to retrieve a connection from this pool that exceeds the set limit will throw an
// SQLException.
config.setConnectionTimeout(10000); // 10 seconds
// idleTimeout is the maximum amount of time a connection can sit in the pool. Connections that
// sit idle for this many milliseconds are retried if minimumIdle is exceeded.
config.setIdleTimeout(600000); // 10 minutes

Node.js

// 'acquireTimeoutMillis' is the number of milliseconds before a timeout occurs when acquiring a
// connection from the pool. This is slightly different from connectionTimeout, because acquiring
// a pool connection does not always involve making a new connection, and may include multiple retries.
// when making a connection
config.pool.acquireTimeoutMillis = 60000; // 60 seconds
// 'createTimeoutMillis` is the maximum number of milliseconds to wait trying to establish an
// initial connection before retrying.
// After acquireTimeoutMillis has passed, a timeout exception will be thrown.
config.pool.createTimeoutMillis = 30000; // 30 seconds
// 'idleTimeoutMillis' is the number of milliseconds a connection must sit idle in the pool
// and not be checked out before it is automatically closed.
config.pool.idleTimeoutMillis = 600000; // 10 minutes

DO#

// Timeout sets the time to wait (in seconds) while
// trying to establish a connection before terminating the attempt.
connectionString.Timeout = 15;

Ir

El paquete database/sql actualmente no ofrece ninguna función para configurar el tiempo de espera de la conexión. Este tiempo se configura a nivel del controlador.

Rubí

# 'timeout' is the maximum number of seconds to wait when retrieving a
# new connection from the pool. After the specified amount of time, an
# ActiveRecord::ConnectionTimeoutError will be raised.
timeout: 5000

PHP

// Here we set the connection timeout to five seconds and ask PDO to
// throw an exception if any errors occur.
[
    PDO::ATTR_TIMEOUT => 5,
    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
]

Duración de la conexión

Limitar la duración de una conexión puede ayudar a evitar la acumulación de conexiones abandonadas. Puedes usar el pool de conexiones para limitar la duración de tus conexiones.

Pitón

# 'pool_recycle' is the maximum number of seconds a connection can persist.
# Connections that live longer than the specified amount of time will be
# re-established
pool_recycle=1800,  # 30 minutes

Java

// maxLifetime is the maximum possible lifetime of a connection in the pool. Connections that
// live longer than this many milliseconds will be closed and reestablished between uses. This
// value should be several minutes shorter than the database's timeout value to avoid unexpected
// terminations.
config.setMaxLifetime(1800000); // 30 minutes

Node.js

La biblioteca Node.js ' knex ' actualmente no ofrece ninguna funcionalidad para controlar la duración de una conexión.

DO#

// ConnectionIdleLifetime sets the time (in seconds) to wait before
// closing idle connections in the pool if the count of all
// connections exceeds MinPoolSize.
connectionString.ConnectionIdleLifetime = 300;

Ir

// Set Maximum time (in seconds) that a connection can remain open.
db.SetConnMaxLifetime(1800 * time.Second)

Rubí

ActiveRecord actualmente no ofrece ninguna funcionalidad para controlar la duración de una conexión.

PHP

PDO actualmente no ofrece ninguna funcionalidad para controlar la duración de una conexión.

Para ver la solicitud completa, haga clic en el siguiente enlace.

Pitón

Ver la aplicación completa para el lenguaje de programación Python.

Java

Ver la aplicación completa para el lenguaje de programación Java.

Node.js

Ver la aplicación completa para el lenguaje de programación Node.js.

DO#

Ver la aplicación completa para el lenguaje de programación C#.

Ir

Ver la aplicación completa para el lenguaje de programación Go.

Rubí

Ver la aplicación completa para el lenguaje de programación Ruby.

PHP

Ver la aplicación completa para el lenguaje de programación PHP.

¿Qué sigue?