데이터베이스로 실험 데이터 영구 저장하기
지금까지 Express 서버에서 시료 데이터를 배열로 관리했습니다. 문제는 — 서버를 껐다 켜면 데이터가 전부 사라진다는 것입니다. 배열은 메모리에만 존재하기 때문입니다.
실험실에서 결과를 포스트잇에 적어놓으면 바람에 날아가지만, 실험 노트에 적으면 영구히 남습니다. 데이터베이스는 웹 개발의 실험 노트입니다 — 서버가 꺼져도, 컴퓨터를 재시작해도, 데이터는 안전하게 남아 있습니다.
파일 vs 데이터베이스
앞에서 Node.js로 CSV 파일을 읽고 쓰는 법을 배웠습니다. 파일도 데이터를 저장할 수 있는데, 왜 데이터베이스가 필요할까요?
| 파일 (CSV, JSON) | 데이터베이스 | |
|---|---|---|
| 검색 | 전체 파일을 읽고 직접 필터링 | 조건만 말하면 즉시 검색 |
| 동시 접근 | 여러 프로그램이 동시에 쓰면 충돌 | 안전하게 동시 접근 처리 |
| 데이터량 | 수만 건이면 느려짐 | 수백만 건도 빠르게 처리 |
| 구조 강제 | 아무 형식이나 저장 가능 (실수 위험) | 컬럼 타입을 강제 (INT, VARCHAR 등) |
실험 시료가 10개면 엑셀이면 충분합니다. 하지만 10만 건의 시료 데이터에서 "OD 1.0 이상이면서 2024년 3월에 등록된 혈액 시료만" 찾으려면 — 파일로는 고통스럽고, 데이터베이스에서는 한 줄이면 됩니다.
SQL: 데이터베이스와 대화하는 언어
데이터베이스에게 "이 데이터 줘", "이것 저장해", "이것 삭제해"라고 말하는 언어가 SQL(Structured Query Language)입니다.
SQL은 프로그래밍 언어 중 가장 쉬운 축에 속합니다. 영어 문장처럼 읽힙니다:
SELECT name, od FROM samples WHERE status = 'fail';이 한 줄은 "samples 테이블에서 status가 fail인 행의 name과 od를 가져와"라는 뜻입니다. PubMed에서 키워드 조합으로 논문을 검색하듯, SQL은 조건 조합으로 데이터를 검색합니다.
테이블: 데이터의 구조
데이터베이스에서 데이터는 테이블(table) 안에 저장됩니다. 엑셀 시트와 거의 같은 구조입니다:
samples 테이블
┌────┬──────────────┬──────┬────────┬────────────┐
│ id │ name │ od │ status │ created_at │
├────┼──────────────┼──────┼────────┼────────────┤
│ 1 │ Blood-A │ 1.85 │ pass │ 2026-03-01 │
│ 2 │ Tissue-B │ 0.42 │ fail │ 2026-03-02 │
│ 3 │ Serum-C │ 2.10 │ pass │ 2026-03-03 │
│ 4 │ Plasma-D │ 0.15 │ fail │ 2026-03-03 │
└────┴──────────────┴──────┴────────┴────────────┘용어 정리:
- 행(Row) = 레코드 하나. 시료 한 건의 전체 정보
- 열(Column) = 항목 하나. 이름, OD 값, 상태 같은 데이터 종류
- 스키마(Schema) = 테이블의 설계도. 어떤 컬럼이 있고, 각 컬럼의 데이터 타입이 무엇인지
CRUD: 데이터의 4가지 기본 동작
모든 정보 시스템의 핵심은 CRUD — 생성, 읽기, 수정, 삭제입니다. 실험실 LIMS도 결국 이 네 가지 동작의 조합입니다.
CREATE — 테이블 만들기
CREATE TABLE samples (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
od DECIMAL(5, 2),
status VARCHAR(10) DEFAULT 'pending',
created_at DATE
);INT— 정수 (id, 시료 번호)VARCHAR(100)— 최대 100자 문자열 (시료 이름)DECIMAL(5, 2)— 소수점 포함 숫자 (OD 값: 소수점 아래 2자리)AUTO_INCREMENT— 새 행이 추가될 때 자동으로 번호가 올라감PRIMARY KEY— 이 컬럼이 각 행의 고유 식별자NOT NULL— 빈 값을 허용하지 않음
프로토콜에 "반응 온도: ___°C, 시간: ___분"이라고 칸을 만들어 놓는 것과 같습니다. 구조를 먼저 정의하면, 나중에 엉뚱한 데이터가 들어오는 것을 방지합니다.
INSERT — 데이터 넣기
INSERT INTO samples (name, od, status, created_at) VALUES
('Blood-A', 1.85, 'pass', '2026-03-01'),
('Tissue-B', 0.42, 'fail', '2026-03-02'),
('Serum-C', 2.10, 'pass', '2026-03-03');id는 AUTO_INCREMENT이므로 직접 넣지 않아도 자동으로 1, 2, 3이 배정됩니다.
SELECT — 데이터 꺼내기 (가장 많이 쓰는 명령)
-- 전체 조회
SELECT * FROM samples;
-- 특정 컬럼만
SELECT name, od FROM samples;
-- 조건 검색
SELECT * FROM samples WHERE status = 'fail';
-- OD 1.0 이상만, OD 높은 순으로 정렬
SELECT name, od FROM samples WHERE od >= 1.0 ORDER BY od DESC;
-- 몇 건인지 세기
SELECT COUNT(*) FROM samples WHERE status = 'pass';SQL의 힘은 이 조건 조합에 있습니다. 파일을 열어서 for문으로 하나하나 비교할 필요 없이, 조건만 말하면 데이터베이스가 최적의 방법으로 찾아줍니다.
UPDATE — 데이터 수정
-- id가 2인 시료의 OD를 재측정 값으로 업데이트
UPDATE samples SET od = 0.98, status = 'pass' WHERE id = 2;WHERE 조건을 빠뜨리면 모든 행이 수정됩니다. 실험 노트에서 한 시료의 결과만 수정하려다 전체를 덮어쓰는 사고와 같습니다. UPDATE와 DELETE에는 반드시 WHERE를 확인하세요.
DELETE — 데이터 삭제
-- 특정 시료 삭제
DELETE FROM samples WHERE id = 4;
-- 모든 fail 시료 삭제 (주의!)
DELETE FROM samples WHERE status = 'fail';관계형 데이터베이스: 테이블 간의 연결
"관계형"이라는 이름은 테이블끼리 관계를 맺을 수 있다는 뜻입니다. 시료 테이블과 연구자 테이블을 연결하면:
researchers 테이블 samples 테이블
┌────┬──────────┐ ┌────┬──────────┬───────────────┐
│ id │ name │ │ id │ name │ researcher_id │
├────┼──────────┤ ├────┼──────────┼───────────────┤
│ 1 │ 김연구 │◄─────────│ 1 │ Blood-A │ 1 │
│ 2 │ 박분석 │◄─────────│ 2 │ Tissue-B │ 2 │
└────┴──────────┘ │ 3 │ Serum-C │ 1 │
└────┴──────────┴───────────────┘researcher_id가 두 테이블을 연결하는 열쇠입니다. 이 구조가 있으면:
SELECT samples.name, researchers.name
FROM samples
JOIN researchers ON samples.researcher_id = researchers.id
WHERE researchers.name = '김연구';"김연구 연구원이 등록한 시료만 보여줘" — 엑셀에서 VLOOKUP을 쓰는 것과 비슷하지만, 수백만 건에서도 순식간에 동작합니다.
어떤 데이터베이스를 쓸까
| 제품 | 특징 | 적합한 상황 |
|---|---|---|
| MySQL | 가장 널리 쓰이는 오픈소스 DB | 웹 서비스 (WordPress, 대부분의 웹사이트) |
| PostgreSQL | 고급 기능, 복잡한 쿼리에 강함 | 분석 중심 서비스, Supabase |
| SQLite | 설치 불필요, 파일 하나가 DB 전체 | 로컬 앱, 프로토타입, 개인 프로젝트 |
BioPlayground 같은 프로젝트는 Supabase(PostgreSQL 기반)를 사용합니다. SQL 문법은 MySQL이든 PostgreSQL이든 90% 이상 동일하므로, 하나만 배우면 나머지는 쉽게 적응할 수 있습니다.
직접 해보기 (Faded Example)
아래 빈칸을 채워 QC 실패 시료를 검색하는 SQL 쿼리를 완성하세요.
name, odFROMstatus = 'fail'ORDER BY od ;
흔한 에러 & 해결법
Q: Table 'database.samples' doesn't exist 에러가 납니다
CREATE TABLE을 아직 실행하지 않았거나, 다른 데이터베이스에 접속한 상태입니다. SHOW TABLES;로 현재 데이터베이스의 테이블 목록을 확인하세요.
Q: UPDATE로 한 행만 수정하려 했는데 전부 바뀌었습니다
WHERE 조건을 빠뜨렸습니다. UPDATE samples SET status = 'pass'는 모든 행의 status를 pass로 바꿉니다. 반드시 WHERE id = 2 같은 조건을 추가하세요. 중요한 작업 전에는 같은 WHERE 조건으로 SELECT를 먼저 실행해서 어떤 행이 영향받는지 확인하는 습관을 기르세요.
Q: VARCHAR과 TEXT의 차이가 뭔가요?
VARCHAR(100)은 최대 100자까지 저장하며 길이를 지정합니다. TEXT는 길이 제한이 거의 없습니다. 시료 이름이나 상태 같은 짧은 데이터는 VARCHAR, 실험 메모나 긴 설명은 TEXT가 적합합니다.
Q: SQL 명령어가 대문자여야 하나요?
아닙니다. SELECT와 select는 같습니다. 대문자는 관례일 뿐입니다. SQL 키워드를 대문자로, 테이블/컬럼 이름을 소문자로 쓰면 코드를 읽기 쉽습니다.
Q: 터미널 말고 GUI로 MySQL을 볼 수 있는 도구가 있나요?
MySQL을 터미널에서 mysql -u root -p로 접속하는 것이 기본이지만, GUI 클라이언트를 쓰면 테이블 구조와 데이터를 시각적으로 확인할 수 있습니다.
| 도구 | 특징 | 가격 |
|---|---|---|
| Sequel Ace | 가볍고 빠름. 기본 쿼리/테이블 관리에 최적 (Mac 전용) | 무료 |
| TablePlus | 세련된 UI. MySQL, PostgreSQL, SQLite 등 여러 DB 지원 | 일부 무료 |
| MySQL Workbench | MySQL 공식 도구. 기능이 많지만 무거움 | 무료 |
처음에는 Sequel Ace(Mac) 또는 MySQL Workbench(전 플랫폼)로 시작하는 것을 추천합니다. SQL 명령어를 직접 치는 것에 익숙해진 후 GUI를 보조로 쓰면, 데이터를 한눈에 확인하면서 작업할 수 있습니다.