🧑🏽‍💻Dev:Lang/GO

GO 언어로 동일한 데이터베이스 테이블 Row 비교하기: 고루틴 이용

Diven 2026. 3. 23. 12:36

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