Gestionar conexiones de bases de datos

En esta página se proporcionan prácticas recomendadas y ejemplos de código específicos de cada lenguaje para ayudarte a crear aplicaciones que usen las conexiones de bases de datos de Cloud SQL de forma eficaz.

Estas muestras son fragmentos de una aplicación web completa que puedes consultar en GitHub. Más información

Para ver instrucciones detalladas sobre cómo ejecutar una aplicación web de ejemplo conectada a Cloud SQL, sigue el enlace correspondiente a tu entorno:

Grupos de conexión

Un grupo de conexiones es una caché de conexiones de bases de datos que se comparten y reutilizan para mejorar la latencia y el rendimiento de las conexiones. Cuando tu aplicación necesita una conexión de base de datos, toma prestada una de su grupo temporalmente. Cuando la aplicación termina de usar la conexión, la devuelve al grupo, donde se puede reutilizar la próxima vez que la aplicación necesite una conexión de base de datos.

Abrir y cerrar conexiones

Cuando usas un grupo de conexiones, debes abrir y cerrar las conexiones correctamente para que siempre se devuelvan al grupo cuando hayas terminado de usarlas. Las conexiones que no se devuelven o que se "filtran" no se reutilizan, lo que supone un desperdicio de recursos y puede provocar cuellos de botella en el rendimiento de tu aplicación.

Python

# 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

try {
  const stmt = 'INSERT INTO votes (time_cast, candidate) VALUES (?, ?)';
  // Pool.query automatically checks out, uses, and releases a connection
  // back into the pool, ensuring it is always returned successfully.
  await pool.query(stmt, [timestamp, team]);
} catch (err) {
  // If something goes wrong, handle the error in this section. This might
  // involve retrying or adjusting parameters depending on the situation.
  // ...
}

C#

using MySql.Data.MySqlClient;
using System;

namespace CloudSql
{
    public class MySqlTcp
    {
        public static MySqlConnectionStringBuilder NewMysqlTCPConnectionString()
        {
            // Equivalent connection string:
            // "Uid=<DB_USER>;Pwd=<DB_PASS>;Host=<INSTANCE_HOST>;Database=<DB_NAME>;"
            var connectionString = new MySqlConnectionStringBuilder()
            {
                // 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.
                Server = Environment.GetEnvironmentVariable("INSTANCE_HOST"),   // e.g. '127.0.0.1'
                // Set Host to 'cloudsql' when deploying to App Engine Flexible environment
                UserID = 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 = MySqlSslMode.Disabled,
            };
            connectionString.Pooling = true;
            // Specify additional properties here.
            return connectionString;

        }
    }
}

Go

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

Ruby

@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
    );
}

Número de conexiones

Cada conexión de base de datos usa recursos del lado del cliente y del lado del servidor. Además, Cloud SQL impone límites de conexión generales que no se pueden superar. Si creas y usas menos conexiones, se reduce la sobrecarga y puedes mantenerte por debajo del límite de conexiones.

Python

# 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

// 'connectionLimit' is the maximum number of connections the pool is allowed
// to keep at once.
connectionLimit: 5,

C#

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

Go

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

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

Ruby

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

PHP

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

Tiempo de espera exponencial

Si tu aplicación intenta conectarse a la base de datos y no lo consigue, es posible que la base de datos no esté disponible temporalmente. En este caso, enviar solicitudes de conexión repetidas desperdicia recursos. Es preferible esperar antes de enviar solicitudes de conexión adicionales para permitir que se pueda volver a acceder a la base de datos. Para conseguirlo, se puede usar un tiempo de espera exponencial u otro mecanismo de retraso.

Este reintento solo tiene sentido cuando se conecta por primera vez o cuando se obtiene una conexión del grupo por primera vez. Si se producen errores durante una transacción, la aplicación debe volver a intentarlo desde el principio de la transacción. Por lo tanto, aunque tu grupo esté configurado correctamente, es posible que la aplicación siga mostrando errores si se pierden las conexiones.

Python

# 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

// The mysql module automatically uses exponential delays between failed
// connection attempts.

C#

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

Go

Actualmente, el paquete database/sql no ofrece ninguna función para configurar el retroceso exponencial.

Ruby

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

PHP

Actualmente, PDO no ofrece ninguna función para configurar la retirada exponencial.

Tiempo de espera agotado

Hay muchos motivos por los que un intento de conexión puede no tener éxito. La comunicación de red nunca está garantizada y es posible que la base de datos no pueda responder temporalmente. Asegúrate de que tu aplicación gestione correctamente las conexiones rotas o fallidas.

Python

# '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

// 'connectTimeout' is the maximum number of milliseconds before a timeout
// occurs during the initial connection to the database.
connectTimeout: 10000, // 10 seconds
// 'acquireTimeout' is the maximum number of milliseconds to wait when
// checking out a connection from the pool before a timeout error occurs.
acquireTimeout: 10000, // 10 seconds
// 'waitForConnections' determines the pool's action when no connections are
// free. If true, the request will queued and a connection will be presented
// when ready. If false, the pool will call back with an error.
waitForConnections: true, // Default: true
// 'queueLimit' is the maximum number of requests for connections the pool
// will queue at once before returning an error. If 0, there is no limit.
queueLimit: 0, // Default: 0

C#

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

Go

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

Ruby

# '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,
]

Finalizar una conexión

Los usuarios con el privilegio PROCESS en Cloud SQL pueden enumerar las conexiones que no gestionan. En MySQL 5.7.x, los usuarios deben tener el privilegio SUPER y, en MySQL 8.0.x, deben tener el privilegio CONNECTION_ADMIN para ejecutar una instrucción KILL en estas conexiones. La instrucción KILL finaliza la conexión de cualquier otro usuario de MySQL (excepto los usuarios administrativos de Cloud SQL). Los usuarios que no tengan estos privilegios solo podrán enumerar y finalizar las conexiones que gestionen.

Puedes enumerar las conexiones a una instancia con el cliente mysql y ejecutar el comando SHOW PROCESSLIST. Usa Id para finalizar la conexión. Por ejemplo:

mysql> SHOW PROCESSLIST;
mysql> KILL 6;

Duración de la conexión

Limitar la duración de una conexión puede ayudar a evitar que se acumulen conexiones abandonadas. Puedes usar el grupo de conexiones para limitar la duración de las conexiones.

Python

# '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

Actualmente, la biblioteca de Node.js mysql no ofrece ninguna función para controlar la duración de una conexión.

C#

// ConnectionLifeTime sets the lifetime of a pooled connection
// (in seconds) that a connection lives before it is destroyed
// and recreated. Connections that are returned to the pool are
// destroyed if it's been more than the number of seconds
// specified by ConnectionLifeTime since the connection was
// created. The default value is zero (0) which means the
// connection always returns to pool.
connectionString.ConnectionLifeTime = 1800; // 30 minutes

Go

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

Ruby

Actualmente, ActiveRecord no ofrece ninguna función para controlar la duración de una conexión.

PHP

Actualmente, PDO no ofrece ninguna función para controlar la duración de una conexión.

Para ver la solicitud completa, haz clic en el enlace que aparece a continuación.

Python

Consulta la aplicación completa del lenguaje de programación Python.

Java

Consulta la aplicación completa del lenguaje de programación Java.

Node.js

Consulta la aplicación completa del lenguaje de programación Node.js.

C#

Consulta la aplicación completa del lenguaje de programación C#.

Go

Consulta la solicitud completa del lenguaje de programación Go.

Ruby

Consulta la aplicación completa del lenguaje de programación Ruby.

PHP

Consulta la aplicación completa del lenguaje de programación PHP.

Siguientes pasos