ניהול חיבורים למסד נתונים

בדף הזה מפורטות שיטות מומלצות ודוגמאות קוד ספציפיות לשפה, שיעזרו לכם ליצור אפליקציות שמשתמשות בחיבורים למסד נתונים של Cloud SQL בצורה יעילה.

הדוגמאות האלה הן קטעים מתוך אפליקציית אינטרנט מלאה שזמינה לכם ב-GitHub. מידע נוסף

כדי לקבל הוראות מפורטות להרצת אפליקציית אינטרנט לדוגמה שמחוברת ל-Cloud SQL, לוחצים על הקישור שמתאים לסביבה שלכם:

מאגרי חיבורים

מאגר חיבורים הוא מטמון של חיבורים למסד נתונים שמשותפים ונעשה בהם שימוש חוזר כדי לשפר את זמן האחזור והביצועים של החיבור. כשהאפליקציה צריכה חיבור למסד נתונים, היא שואלת חיבור זמני ממאגר החיבורים. כשהאפליקציה מסיימת להשתמש בחיבור, היא מחזירה אותו למאגר, כך שאפשר יהיה להשתמש בו שוב בפעם הבאה שהאפליקציה תצטרך חיבור למסד נתונים.

פתיחה וסגירה של חיבורים

כשמשתמשים במאגר חיבורים, צריך לפתוח ולסגור את החיבורים בצורה נכונה, כדי שהחיבורים תמיד יוחזרו למאגר כשמסיימים להשתמש בהם. חיבורים שלא הוחזרו או ש "דלפו" לא מנוצלים מחדש, מה שגורם לבזבוז משאבים ועלול לגרום לצווארי בקבוק בביצועים של האפליקציה.

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

מספר החיבורים

כל חיבור למסד נתונים משתמש במשאבים בצד הלקוח ובצד השרת. בנוסף, ב-Cloud SQL יש מגבלות חיבור כוללות שאסור לחרוג מהן. יצירה ושימוש בפחות חיבורים מצמצמים את התקורה ועוזרים לכם לא לחרוג ממגבלת החיבורים.

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

בשלב הזה, PDO לא מציעה פונקציונליות להגדרת מגבלות על חיבורים.

השהיה מעריכית לפני ניסיון חוזר (exponential backoff)

אם האפליקציה מנסה להתחבר למסד הנתונים ולא מצליחה, יכול להיות שמסד הנתונים לא זמין באופן זמני. במקרה כזה, שליחה חוזרת של בקשות חיבור מבזבזת משאבים. מומלץ להמתין לפני שליחת בקשות חיבור נוספות כדי לאפשר גישה חוזרת למסד הנתונים. כדי להשיג את המטרה הזו, אפשר להשתמש בהשהיה מעריכית לפני ניסיון חוזר (exponential backoff) או במנגנון השהיה אחר.

ניסיון החיבור מחדש הזה הגיוני רק כשמתחברים בפעם הראשונה, או כשמשיגים חיבור מהמאגר בפעם הראשונה. אם מתרחשות שגיאות באמצע עסקה, האפליקציה צריכה לנסות שוב, והיא צריכה לנסות שוב מההתחלה של העסקה. לכן, גם אם המאגר מוגדר בצורה נכונה, יכול להיות שהאפליקציה עדיין תציג שגיאות אם החיבורים יאבדו.

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

החבילה database/sql לא מציעה כרגע פונקציונליות להגדרת השהיה מעריכית לפני ניסיון חוזר (exponential backoff).

Ruby

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

PHP

בשלב הזה, PDO לא מציעה פונקציונליות להגדרת השהיה מעריכית לפני ניסיון חוזר (exponential backoff).

תם הזמן הקצוב לחיבור

יש הרבה סיבות לכך שניסיון ליצור חיבור לא יצליח. אין ערובה לכך שתהיה תקשורת עם הרשת, ויכול להיות שהמסד נתונים לא יוכל להגיב באופן זמני. חשוב לוודא שהאפליקציה מטפלת בחיבורים שנכשלו או נותקו בצורה תקינה.

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

נכון לעכשיו, חבילת database/sql לא מציעה פונקציונליות להגדרת זמן קצוב לתפוגה לחיבור. הזמן הקצוב לתפוגה מוגדר ברמת הנהג.

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

סיום חיבור

משתמשים עם הרשאת PROCESS ב-Cloud SQL יכולים לראות רשימה של חיבורים שהם לא מנהלים. ב-MySQL 5.7.x, למשתמשים צריכה להיות הרשאת SUPER, וב-MySQL 8.0.x, למשתמשים צריכה להיות הרשאת CONNECTION_ADMIN כדי להריץ הצהרת KILL בחיבורים האלה. ההצהרה KILL מסיימת את החיבור של כל משתמש אחר ב-MySQL (למעט משתמשי אדמין ב-Cloud SQL). משתמשים ללא ההרשאות האלה יכולים רק להציג ולסיים חיבורים שהם מנהלים.

אפשר לרשום את החיבורים למופע באמצעות לקוח mysql והרצת הפקודה SHOW PROCESSLIST. משתמשים בId כדי לסיים את החיבור. לדוגמה:

mysql> SHOW PROCESSLIST;
mysql> KILL 6;

משך החיבור

הגבלת משך החיים של חיבור יכולה לעזור למנוע הצטברות של חיבורים לא פעילים. אפשר להשתמש במאגר החיבורים כדי להגביל את משך החיים של החיבורים.

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

ספריית Node.js‏ mysql לא מציעה כרגע פונקציונליות לשליטה במשך החיבור.

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

בשלב הזה, ActiveRecord לא מציעה פונקציונליות לשליטה במשך החיבור.

PHP

בשלב הזה, PDO לא מציעה פונקציונליות לשליטה במשך החיבור.

כדי לראות את הבקשה המלאה, לוחצים על הקישור שלמטה.

Python

אפשר לראות את האפליקציה המלאה לשפת התכנות Python.

Java

אפשר לראות את הבקשה המלאה בשפת התכנות Java.

Node.js

אפשר לעיין ביישום המלא של שפת התכנות Node.js.

C#‎

אפשר לעיין בבקשה המלאה לשפת התכנות C#.

Go

אפשר לעיין בבקשה המלאה לשפת התכנות Go.

Ruby

אפשר לראות כאן את הבקשה המלאה לשפת התכנות Ruby.

PHP

אפשר לעיין בבקשה המלאה לשפת התכנות PHP.

המאמרים הבאים