แรกแย้มกับการใช้ฐานข้อมูล SQL แบบนูบๆ

harddrive, hdd, computer, object

การใช้งาน Database แบบ SQL ที่ Go ใช้งานได้มีหลากหลาย หาอ่านได้จากหน้าลิสต์ไดร์เวอร์ นี้ https://github.com/golang/go/wiki/SQLDrivers

โดยพื้นฐานจะใช้งานได้ต้องเริ่มจาก Import แพคเกจ database/sql มาใช้พร้อมกับไดร์เวอร์ก่อน เช่นเปิด sqlite3

"database/sql"
_ "github.com/mattn/go-sqlite3"

หากสงสัยว่าทำไมต้องมีไอ้อันเดอร์สกอร์ _ ก่อนเรียกไดร์ฟเวอร์ด้วย คือโปรแกรมเราไม่ได้เรียกใช้มันโดยตรง จึงต้องบอกให้คอมไพลเลอร์ละมันไว้ ไม่ต้องมาด่าว่าเชิญเทพมาแล้วทำไมไม่เรียกใช้งาน เพราะจริงๆแล้วมีแค่ตัวแพคเกจ database/sql เป็นตัวเรียกใช้ เปรียบง่ายๆ ก็ให้นึกภาพว่าเราอัญเชิญ เทพ “sqlite3″ มาประทับร่าง “database/sql” ตัวแพคเกจ “database/sql” มันเป็นแค่ “ร่างทรง” ภาษากี๊กกี้เราเรียกว่า Interface หรือ Abstraction Layer ดังนั้นวันหลังไปกราบคนทรงเจ้าที่ไหน ให้ทักว่าเค้าก็คือ Abstraction Layer ทำหน้าที่เป็น Interface ของเทพที่เรากำลังกราบอยู่ไงละ! ดังนั้นเราทำอะไรกับเทพองค์ไหนก็ได้ผ่านคนทรง จะกอด จะจูบ จะลูบ จะคลำ ผ่านคนทรงคนเดียวกันได้หมดโดยที่เราไม่ต้องเป็นเทพก็ได้ ง่ายจริง  …เอ่อชักนอกเรื่องละ สรุปคือเราเสกก้อนอะไรสักอย่างที่ข้างในมีฐานข้อมูลที่เราต้องการใช้ขึ้นมา โดยไม่ต้องสนใจรายละเอียดลึกๆ ข้างในของ DB แต่ละประเภทว่าต่างกันอย่างไร เพราะเราจะมีคำสั่งมาตรฐานง่ายๆให้ใช้เหมือนๆกันหมด

หลังจากนั้นเรามักเริ่มต้นฟังค์ชั่นใดๆที่จะเรียกใช้ฐานข้อมูลด้วยการเรียกฟังค์ชั่น Open() มีรูปแบบดังนี้

db, err := sql.Open(driver, dataSourceName)

ส่วนจะกรอก driver ด้วยคำว่าอะไร หรือ dataSourceName อะไร ก็หาอ่านดูได้จากคู่มือของไดร์ฟเวอร์แต่ละตัว ตัวอย่างเช่น

db, err = sql.Open("sqlite3","./server.db")

แปลว่าเราได้เปิด sqlite ที่เก็บไฟล์ไว้โฟลเดอร์เดียวกับโปรแกรมหลักของเราชื่อ server.db โดยจะคืนค่ามา 2 ตัวคือ

  1. Pointer ของ DB Object (*sql.DB) ย้ำว่าเป็นพ้อยท์เตอร์นะ ไม่ใช้สำเนาของตัว sql.DB เอง แปลง่ายๆว่า อ่าน และ เขียนข้อมูลจาก Object ตัวนี้ได้นะจร้า
  2. err ตามธรรมเนียมนิยมของฟังค์ชั่นใน Go ที่มักจะคืนค่า Err มาให้ตรวจสอบข้อผิดพลาดด้วยเสมอ ซึ่งก็ควรดัก err เอาไว้ตามธรรมเนียมในบรรทัดถัดไปประมาณนี้
if err != nil {
   log.Panic("Cannot Open DB: ", err)
}

จุดสำคัญ sql.Open() เป็นเพียงการสร้าง *sql.DB ซึ่งเป็น Abstraction Layer ขึ้นมาในระบบ แต่ทั้งนี้มันเป็นแค่ก้อน Abstrct Layer แต่ยังไม่มีการเชื่อมโยงเกิดขึ้นจริงๆ ดังนั้นก่อนจะสั่งคิวรี่ปล่อยไก่อะไรให้คอมไพลเลอร์ด่า เราก็สั่งเช็คการเชื่อมต่อนิสนึงด้วยเมธอด db.Ping นี้นะ

if err := db.Ping(); err != nil {
  log.Fatal(err)
}

การปิงก็เหมือนกับเราสั่งคนทรงเจ้าให้กดโทรศัพท์หาเทพ พอเค้ายกหูก็รีบวางสาย…นิสัยไม่ดี เอ้ย ไม่ใช่ เค้าก็แค่คืนการเชื่อมต่อกลับไปยัง connection pool ทันทีที่รู้ว่าต่อสายได้จริง เอ๊ะ มันคืออัลลัย? ช่างมันก่อนหลังจากนี้ก็อาจใช้ คำสั่ง defer ให้ไม่ลืมปิดการเชื่อมต่อโดยอัตโนมัติเมื่อจบ Scope

defer db.Close()

แต่ทีนี้ถ้าอยากลากยาว ก็มีหลายท่า ท่าง่ายสุดก็ลองประกาศตัวแปร var db = sql.Open(…) ทิ้งเอาไว้เป็น Package variable ข้างนอกฟังค์ชั่น เพื่อให้มี Object *sql.DB ใช้งานจนกว่าจะสั่งปิด และอย่าลืมสั่งปิดเสมอ ไม่งั้นเวลาคนใช้เยอะๆ สักพัก Server เราจะมีอันเป็นไปโดยไม่รู้สาเหตุได้ง่ายๆ

โดยปกติ เรามักจะเปิด sql.Open() ทิ้งไว้ยาวๆ แล้วเราก็ต้องไปเข้าใจการทำงานของสิ่งที่เรียกว่า Connection Pool ซึ่งเราจะเล่าถึงมันในโอกาสต่อไป

อ้างอิงจาก https://github.com/golang/go/wiki/SQLInterface