SQL 데이터베이스에 데이터를 로드하는 방법: 단순 삽입에서 대량 로드까지

데이터 저장소는 트랜잭션 데이터베이스 시스템의 가장 중요한 부분입니다. 이 기사에서는 트랜잭션 SQL 시스템에 데이터를 로드하는 다양한 방법에 대해 자세히 설명합니다. 몇 개의 레코드를 삽입하는 것부터 수백만 개의 레코드에 이르기까지.

데이터베이스에 데이터를 삽입하는 첫 번째이자 가장 쉬운 방법은 ORM(Object-Relational Mapping) 도구를 사용하는 것입니다. 이 튜토리얼의 목적을 위해 저는 Rails의 ActiveRecord를 ORM 작업의 데모로 사용할 것입니다. ORM을 통해 데이터를 삽입하면 모든 비즈니스 규칙 및 유효성 검사가 실행되므로 걱정할 필요가 없습니다. 다음과 같이 하면 쉽습니다.

Users.create(username: "John Doe", role: "admin")

이 방법의 단점은 확장되지 않는다는 것입니다. 각 레코드에 대해 모델 객체를 생성하고 검증 및 비즈니스 규칙을 위해 첨부된 콜백을 실행하며 DML 트랜잭션을 실행합니다.

일부 프레임워크는 대신 대량 삽입을 수행하는 방법을 제공합니다. 단일INSERT SQL 쿼리가 준비되고 단일 sql 문이 모델을 인스턴스화하거나 모델 콜백 또는 유효성 검사를 호출하지 않고 데이터베이스로 전송됩니다. 예를 들어, Rails 6에서는 다음과 같습니다.

result = Users.insert_all(
  [
    { id: 1,
      username: 'John Doe',
      role: 'admin' },
    { id: 2,
      username: 'Jane Doe',
      role: 'admin' },
  ]
)

또는 정확히 동일한 효과를 가지므로 원시 SQL로 수행할 수 있습니다.

INSERT INTO
  users(id, username, role)
  VAULES
  (1, "John Doe", "admin"),
  (2, 'Jane Doe', "admin");

두 솔루션 모두 충돌(예: 기본 키 고유성 또는 부적합한 데이터 유형 위반)이 있거나 비즈니스 역할 또는 상위 수준 응용 프로그램 유효성 검사 규칙을 위반할 수 있습니다. 두 경우 모두 프로그래머는 데이터베이스 수준 오류를 처리하고 상위 수준 규칙의 실행 또는 준수를 보장하여 문제를 처리해야 합니다. 예를 들어 기본 키 열 열의 중복을 무시하려면 쿼리 끝에 다음과 같이 추가할 수 있습니다.

ON CONFLICT (id) DO NOTHING;

로드된 데이터에 데이터베이스의 기존 행에 대한 업데이트가 포함되어 있으면 어떻게 됩니까? 이 작업을 upsert라고 합니다. 테이블에 행이 이미 있는 경우(기본 키에 따라 존재 여부 판별) 전달된 값으로 행이 업데이트됩니다. 그렇지 않으면 새 행으로 삽입됩니다. Rails 6에서는 insert_all 함수를 upsert_all로 바꾸는 것만큼 쉽습니다.

result = Users.upsert_all(
  [
    { id: 1,
      username: 'John Doe',
      role: 'admin' },
    { id: 2,
      username: 'Jane Doe',
      role: 'admin' },
  ]
)

SQL에서는 다음과 같을 것입니다.

INSERT INTO
  users(id, username, role)
  VAULES
    (1, 'John Doe', 'admin'),
    (2, 'Jane Doe', 'admin')
ON DUPLICATE KEY
UPDATE id=VALUES(id), username=VALUES(username), role=VALUES(role);

이 방법의 확장성은 여전히 ​​제한적입니다. 대부분의 서버에는 쿼리 길이에 대한 최대 제한이 있으며 제한이 없더라도 네트워크를 통해 기가바이트 단위의 길이로 쿼리를 보내고 싶지는 않을 것입니다. 간단한 해결책은 UPSERT 일괄 처리하는 것입니다. 다음과 같을 것입니다.

record_num = records.length
batch = 1000
batch_num = record_num / 1000
(1..batch_num).do |n|
  lower_bound = (batch_num - 1) * batch
  higher_bound = batch_num * batch
  Users.upsert_all(records[lower_bound..higher_bound])
end

또는 upsert_all 함수 대신 원시 SQL을 사용합니다. 같은 것.

이 솔루션은 기술적으로 잘 확장됩니다. 그러나 성능을 높이기 위해 대부분의 SQL 데이터베이스에는 파일에서 테이블로 데이터를 로드하는 복사 기능이 있습니다. 해당 기능을 사용하기 위해 데이터는 데이터베이스 엔진이 지원하는 형식(일반적인 형식은 CSV)으로 파일에 덤프됩니다. 그런 다음 테이블은 다음과 같은 SQL 명령으로 파일에서 채워집니다.

COPY users FROM 'path/to/my/csv/file.csv';

이 문은 CSV 파일 내의 모든 데이터를 사용자 테이블에 추가합니다. 이 솔루션은 여러 GB 데이터 규모의 파일에 대해 훨씬 더 나은 성능을 제공할 수 있습니다.

그러나 이 솔루션은 upsert를 처리하지 않습니다. 이렇게 하려면 임시 테이블을 만들고 파일에서 채운 다음 기본 키를 사용하여 원래 테이블에 병합합니다.

CREATE TEMP TABLE tmp_table
...; /* same schema as `users` table */

COPY tmp_table FROM 'path/to/my/csv/file.csv';

INSERT INTO users
  SELECT *
  FROM tmp_table;

이 솔루션은 데이터베이스에 대한 다중 트랜잭션을 방지하고 더 높은 성능을 보장합니다. 데이터베이스에 GB의 데이터를 로드할 때 조사할 가치가 있습니다.

좋은 웹페이지 즐겨찾기