ایجاد استخر اتصال پایگاه داده (Creating a Database Connection Pool)
حالا که درایور MySQL نصب شده است، میتوانیم از آن برای ایجاد یک استخر اتصال (Connection Pool) به پایگاه داده استفاده کنیم.
در Go، استخر اتصال با استفاده از نوع sql.DB از بسته database/sql نمایش داده میشود. این یک مجموعه اتصالات (Connection Pool) را مدیریت میکند، که میتواند شامل اتصالات باز و بسته به پایگاه داده باشد.
برای این کار به تابع sql.Open() گو نیاز داریم که به این صورت استفاده میشود:
// The sql.Open() function initializes a new sql.DB object, which is essentially a // pool of database connections. db, err := sql.Open("mysql", "web:pass@/snippetbox?parseTime=true") if err != nil { ... }
چند نکته مهم در مورد این کد وجود دارد که باید توضیح داده شود:
پارامتر اول تابع
sql.Open()نام درایور (Driver Name) و پارامتر دوم نام منبع داده (Data Source Name یا DSN) است که نحوه اتصال به پایگاه داده شما را توصیف میکند.فرمت نام منبع داده (DSN Format) به پایگاه داده و درایوری که استفاده میکنید بستگی دارد. معمولاً میتوانید اطلاعات و نمونهها را در مستندات درایور خاص خود پیدا کنید.
قسمت
parseTime=trueدر DSN یک پارامتر خاص درایور (Driver-specific Parameter) است که به درایور ما دستور میدهد فیلدهایTIMEوDATESQL را به اشیاءtime.Timeگو تبدیل کند.تابع
sql.Open()یک شیءsql.DBبرمیگرداند. این یک اتصال به پایگاه داده نیست - بلکه یک استخر از بسیاری از اتصالات (Connection Pool) است.استخر اتصال برای دسترسی همزمان (Concurrent Access) امن است، بنابراین میتوانید آن را از هندلرهای برنامه وب به طور ایمن استفاده کنید.
استخر اتصال برای استفاده طولانی مدت طراحی شده است. در یک برنامه وب، معمول است که استخر اتصال را در تابع
main()خود مقداردهی اولیه کنید و سپس استخر را به هندلرهای خود ارسال کنید. نبایدsql.Open()را در یک هندلر HTTP کوتاهمدت فراخوانی کنید - این کار هدر رفتن حافظه و منابع شبکه خواهد بود.
استفاده از آن در برنامه وب ما (Using it in our Web Application)
بیایید ببینیم چگونه میتوانیم از sql.Open() در عمل استفاده کنیم. فایل main.go خود را باز کنید و کد زیر را اضافه کنید:
package main import ( "database/sql" // New import "flag" "log/slog" "net/http" "os" _ "github.com/go-sql-driver/mysql" // New import ) ... func main() { addr := flag.String("addr", ":4000", "HTTP network address") // Define a new command-line flag for the MySQL DSN string. dsn := flag.String("dsn", "web:pass@/snippetbox?parseTime=true", "MySQL data source name") flag.Parse() logger := slog.New(slog.NewTextHandler(os.Stdout, nil)) // To keep the main() function tidy I've put the code for creating a connection // pool into the separate openDB() function below. We pass openDB() the DSN // from the command-line flag. db, err := openDB(*dsn) if err != nil { logger.Error(err.Error()) os.Exit(1) } // We also defer a call to db.Close(), so that the connection pool is closed // before the main() function exits. defer db.Close() app := &application{ logger: logger, } logger.Info("starting server", "addr", *addr) // Because the err variable is now already declared in the code above, we need // to use the assignment operator = here, instead of the := 'declare and assign' // operator. err = http.ListenAndServe(*addr, app.routes()) logger.Error(err.Error()) os.Exit(1) } // The openDB() function wraps sql.Open() and returns a sql.DB connection pool // for a given DSN. func openDB(dsn string) (*sql.DB, error) { db, err := sql.Open("mysql", dsn) if err != nil { return nil, err } err = db.Ping() if err != nil { db.Close() return nil, err } return db, nil }
چند نکته جالب در مورد این کد وجود دارد:
توجه کنید که مسیر import برای درایور ما با یک زیرخط شروع میشود؟ این به این دلیل است که فایل
main.goما در واقع از هیچ چیز در بستهmysqlاستفاده نمیکند. بنابراین اگر سعی کنیم آن را به طور معمول import کنیم، کامپایلر گو خطا میدهد. با این حال، ما به تابعinit()درایور نیاز داریم تا اجرا شود تا بتواند خود را با بستهdatabase/sqlثبت کند. ترفند دور زدن این مشکل، نام مستعار دادن به نام بسته با شناسه خالی است، همانطور که ما اینجا انجام میدهیم. این یک روش استاندارد برای اکثر درایورهای SQL گو است.تابع
sql.Open()در واقع هیچ اتصالی ایجاد نمیکند، فقط استخر را برای استفاده آینده مقداردهی اولیه میکند. اتصالات واقعی به پایگاه داده به صورت تنبل ایجاد میشوند، در اولین زمانی که نیاز میشوند. بنابراین برای تأیید اینکه همه چیز به درستی تنظیم شده است، باید از متدdb.Ping()استفاده کنیم تا یک اتصال ایجاد کنیم و خطاها را بررسی کنیم. اگر خطایی وجود داشته باشد،db.Close()را فراخوانی میکنیم تا استخر اتصال را ببندیم و خطا را برگردانیم.برگشت به تابع
main()، در این لحظه فراخوانیdefer db.Close()کمی اضافی است. برنامه ما فقط با یک سیگنال قطع (یعنیCtrl+C) یا باos.Exit(1)خاتمه مییابد. در هر دو مورد، برنامه بلافاصله خارج میشود و توابع تعویقافتاده هرگز اجرا نمیشوند. اما اطمینان از بستن همیشه استخر اتصال عادت خوبی است که باید به آن عادت کنید و میتواند در آینده مفید باشد اگر یک خاموشسازی نرم به برنامه خود اضافه کنید.
تست اتصال (Testing a Connection)
اطمینان حاصل کنید که فایل ذخیره شده است و سپس سعی کنید برنامه را اجرا کنید. اگر همه چیز طبق برنامه پیش رفته باشد، استخر اتصال باید ایجاد شود و متد db.Ping() باید بتواند یک اتصال بدون هیچ خطایی ایجاد کند. اگر همه چیز خوب باشد، باید پیام لاگ معمولی starting server را مانند زیر ببینید:
$ go run ./cmd/web time=2024-03-18T11:29:23.000+00:00 level=INFO msg="starting server" addr=:4000
اگر برنامه شروع نشد و پیام خطای "Access denied..." مانند زیر دریافت کردید، احتمالاً مشکل در DSN شما است. دوباره بررسی کنید که نام کاربری و رمز عبور صحیح هستند، که کاربران پایگاه داده شما دسترسیهای مناسب دارند و که نمونه MySQL شما از تنظیمات استاندارد استفاده میکند.
$ go run ./cmd/web time=2024-03-18T11:29:23.000+00:00 level=ERROR msg="Error 1045 (28000): Access denied for user 'web'@'localhost' (using password: YES)" exit status 1
مرتب کردن فایل go.mod (Tidying the go.mod File)
حالا که کد ما در واقع درایور github.com/go-sql-driver/mysql را import میکند، میتوانید دستور go mod tidy را اجرا کنید تا فایل go.mod خود را مرتب کنید و هر گونه حاشیهنویسی // indirect غیرضروری را حذف کنید.
$ go mod tidy
پس از انجام این کار، فایل go.mod شما باید مانند زیر به نظر برسد - با github.com/go-sql-driver/mysql به عنوان یک وابستگی مستقیم و filippo.io/edwards25519 همچنان به عنوان یک وابستگی غیرمستقیم.
module snippetbox.letsgofa.net go 1.23.0 require github.com/go-sql-driver/mysql v1.8.1 require filippo.io/edwards25519 v1.1.0 // indirect
واژهنامه اصطلاحات فنی
| اصطلاح فارسی | معادل انگلیسی | توضیح |
|---|---|---|
| استخر اتصال پایگاه داده | Database Connection Pool | مجموعهای از اتصالات پایگاه داده که برای استفاده مجدد مدیریت میشوند |
| استخر اتصال | Connection Pool | مکانیزمی برای مدیریت و استفاده مجدد از اتصالات پایگاه داده |
| مجموعه اتصالات | Connection Set | گروهی از اتصالات پایگاه داده که میتوانند باز یا بسته باشند |
| رشته اتصال | Connection String | متنی که اطلاعات لازم برای اتصال به پایگاه داده را مشخص میکند |
| پیکربندی اتصال | Connection Configuration | تنظیمات مربوط به نحوه برقراری و مدیریت اتصالات پایگاه داده |
| حداکثر اتصالات باز | Maximum Open Connections | بیشترین تعداد اتصالات همزمان مجاز به پایگاه داده |
| حداکثر اتصالات بیکار | Maximum Idle Connections | بیشترین تعداد اتصالات غیرفعال که در استخر نگهداری میشوند |
| زمان انقضای اتصال | Connection Timeout | مدت زمانی که یک اتصال میتواند بدون استفاده باقی بماند |
| مدیریت خودکار اتصال | Automatic Connection Management | سیستم خودکار برای باز و بسته کردن اتصالات بر اساس نیاز |
| بازیابی اتصال | Connection Recovery | فرآیند بازیابی اتصالات از دست رفته یا خراب شده |