
package main
- Go 프로그램의 시작점입니다. main 패키지를 정의하면 이 파일은 실행 가능한 프로그램(entry point)을 만든다는 의미
import (
"database/sql"
"fmt"
"sync"
_ "github.com/go-sql-driver/mysql"
)
구문:
- Go 코드에서 외부 패키지를 불러올 때 사용합니다.
각 줄 설명:
- "database/sql"
- Go에서 SQL 데이터베이스를 사용하기 위한 표준 인터페이스입니다. MySQL뿐 아니라 PostgreSQL, SQLite 등 다양한 DB 드라이버와 함께 사용할 수 있게 도와줘요.
- "fmt"
- 출력 관련 함수들이 있는 패키지입니다. 예: fmt.Println().
- "sync"
- 동시성 제어(멀티스레드)를 위한 패키지예요. 예: sync.WaitGroup, sync.Mutex 등.
- _ "github.com/go-sql-driver/mysql"
- MySQL 드라이버를 임포트하지만 직접 사용하지는 않겠다는 의미로 "_"를 붙입니다.
- 이건 “드라이버를 등록만 하겠다”는 뜻입니다. → sql.Open("mysql", ...)을 쓸 수 있게 만들어주는 역할을 합니다.
var (
username:password@tcp(ip:port)/database_name
)
접속할 DB 형식을 변수로 생성합니다.
🔖 var 란?
Go 언어에서 var는 변수를 선언할 때 사용하는 키워드입니다. 쉽게 말해, 어떤 값을 담아두는 “그릇”을 만드는 역할이에요.
🔖 기본 문법
var 변수명 타입 = 값
// example
// var name string = "HHHH"
// var age int = 26
🧤 타입은 생략 가능
Go는 타입을 자동으로 알아낼 수 있기 때문에, 아래처럼 써도 됩니다:
var name = "HHHH"
var age = 26
또는, 함수 안 에서만 간단히:
name := "HHHH" //(:= 단축 선언)
var tables = []string{
"table1", "Table2", ...
}
- tables: 비교할 테이블 이름들을 배열에 담아놓았습니다. 이 배열의 모든 테이블에 대해 row 수를 DB에서 조회할 수 있어요.
func getRowCount(db *sql.DB, table string) (int, error) {
var count int
query := fmt.Sprintf("SELECT COUNT(*) FROM `%s`", table)
err := db.QueryRow(query).Scan(&count)
return count, err
}
- getRowCount: 주어진 DB와 table을 이용해 SELECT COUNT(*) 쿼리를 날리고 결과를 Count에 저장해요
- fmt.Sprintf(...): 문자열 포맷팅입니다. 테이블 이름을 SQL에 삽입해요
- QueryRow(...).Scan(...): 쿼리 결과의 첫 번째 행의 값을 변수에 저장해요
func main() {
- 프로그램의 시작점입니다
db1, err := sql.Open("mysql", db1DSN)
if err != nil {
panic(err)
}
defer db1.Close()
- DB1에 접속합니다.
- 오류가 나면 프로그램 종료(panic)
- defer db1.Close():main() 함수가 끝날 때 DB 연결을 닫습니다.
fmt.Printf("%-50s | %10s | %10s\n", "Table Name", "DB1 Count", "DB2 Count")
fmt.Println("--------------------------------------------------------------------------")
- %-50s: 왼쪽 정렬로 50자 공간 확보
- %10s: 오른쪽 정렬로 10자 공간 확보
var wg sync.WaitGroup
var mu sync.Mutex
- wg: 여러 고루틴의 완료를 기다리기 위한 WaitGroup
- mu: 출력 시 여러 고루틴이 동시에 fmt.Printf를 사용하면 출력이 꼬일 수 있기 때문에 Mutex로 동기화
for _, table := range tables {
wg.Add(1)
- 테이블 목록을 하나씩 순회하면서 고루틴을 실행할 준비를 합니다.
- wg.Add(1): 하나의 고루틴이 추가됨을 알림
go func(table string) {
defer wg.Done()
- go func() {...}(table): 각 테이블마다 비동기(병렬)로 고루틴을 실행합니다.
- defer wg.Done(): 이 고루틴이 끝나면 WaitGroup에 완료 알림
count1, err1 := getRowCount(db1, table)
count2, err2 := getRowCount(db2, table)
- 해당 테이블의 row 수를 DB1 ,DB2 각각에서 조회합니다.
mu.Lock()
- 여러 고루틴이 동시에 출력하면 글자가 겹치므로, 출력 전 락을 겁니다.
if err1 != nil || err2 != nil {
fmt Printf(""%-50s | %10v | %10v\n", table, "err", "err")
} else {
fmt.Printf("%-50s | %10d | %10d\n", table, count1, count2)
}
- 에러가 있으면 "err" 라고 출력
- 정상일 경우에는 두 DB의 행 개수를 출력합니다.
mu.Unlock()
- 출력이 끝났으면 락을 해제합니다.
전체 Code
package main
import (
"database/sql"
"fmt"
"sync"
_ "github.com/go-sql-driver/mysql"
)
var (
db1DSN = "username:password@tcp(ip:port)/database_name"
db2DSN = "username:password@tcp(ip:port)/database_name"
)
var tables = []string{
"example_table", "example_table", "example_table", "example_table"}
func getRowCount(db *sql.DB, table string) (int, error) {
var count int
query := fmt.Sprintf("SELECT COUNT(*) FROM `%s`", table)
err := db.QueryRow(query).Scan(&count)
return count, err
}
func main() {
db1, err := sql.Open("mysql", db1DSN)
if err != nil {
panic(err)
}
defer db1.Close()
db2, err := sql.Open("mysql", db2DSN)
if err != nil {
panic(err)
}
defer db2.Close()
fmt.Printf("%-50s | %10s | %10s\n", "Table Name", "DB1 Count", "DB2 Count")
fmt.Println("--------------------------------------------------------------------------")
var wg sync.WaitGroup
var mu sync.Mutex
for _, table := range tables {
wg.Add(1)
go func(table string) {
defer wg.Done()
count1, err1 := getRowCount(db1, table)
count2, err2 := getRowCount(db2, table)
// 병렬 출력 충돌 방지
mu.Lock()
if err1 != nil || err2 != nil {
fmt.Printf("%-50s | %10v | %10v\n", table, "err", "err")
} else {
fmt.Printf("%-50s | %10d | %10d\n", table, count1, count2)
}
mu.Unlock()
}(table)
}
wg.Wait()
}