이번에 구름에서 제공하는 단기 KDT, 프로펙트 풀스택 과정에 참여했다. 협업 경험이 부족한 것도 있고 개발자로서 성장하는 방법을 네이버 부스트캠프에서 배웠으니, 직접 실천하며 내 것으로 만드는 과정이 필요하다 생각되어 지원했다. 어찌됐든 구름 프로젝트팀에서 검색과 검색어 자동완성에 대해 기능 구현과 고도화를 맡았다. 이전 프로젝트인 데나무에서의 경험으로 대충 풀텍스트 인덱스로 검색을 한다 정도로만 알고 있다. 다행히도 가상 면접 사례로 배우는 대규모 시스템 설계 기초에서 검색 자동 완성편도 있었다. 우선은 검색이 어떻게 MySQL에서 이루어질 수 있는지 살펴보고 (학습하기) 자동완성이 어떤 식으로 설계되는지 한 번 읽어보는 방식으로 학습하려고 한다.(설계하기)
문제 정의
어떻게 MySQL을 활용해서 검색을 만들 수 있는가?
어떻게 MySQL을 활용해서 검색을 만들 수 있는가?
LIKE
당장 떠오르는 방법은 LIKE 쿼리를 사용해서 검색을 하는 것이다. RDB나 SQL을 학습하면 가장 먼저 볼 수 있는 내용이다. 그만큼 구현하기 쉽고 쉽게 떠올릴 수 있다.
_ 으로 표현되는 한 자리의 와일드카드를 쓰거나, % 로 표현되는 문자열의 와일드카드를 사용해서 문자열을 검색할 수 있다.
SELECT *
FROM employees
WHERE last_name LIKE 'Sm_th';
sql
SELECT *
FROM employees
WHERE last_name LIKE '%mi%';
LIKE의 성능은 어떨까?
LIKE의 성능을 생각해보기 위해서는 LIKE가 어떻게 동작하는 지 알아야 한다. 한 블로그 글을 참고하였다.
아래와 같은 쿼리들을 실행시킬 것이며 table_a라는 테이블의 title 컬럼에는 index가 적용되어 있다고 가정하자. (index에 대한 내용은 다음 절에서 가볍게 정리하고, 더 깊게는 고도화 과정이나 시간이 남을 때 공부하며 정리한다.)
SELECT *
FROM table_a
WHERE title LIKE 'something%';
SELECT *
FROM table_a
WHERE title LIKE '%something';
SELECT *
FROM table_a
WHERE title LIKE '%something%';
LIKE문에서는 특정 문자열의 포함 여부를 체크함에 따라 데이터의 앞부분이 문자열에 포함되는지 체크하는 경우에만 index를 사용한다. 예를 들어, something% 는 index가 데이터 조회에 사용되지만 %something% 이나 %something 은 index가 사용되지 않는 풀스캔을 하게 된다.
💡 Index의 데이터스트럭쳐
Index의 자료구조는 B-Tree 혹은 B+Tree로 이루어져 있다. (이 친구들은 순전히 데이터를 저장하기 위해 태어난 자료구조다.)
B트리는 데이터를 오름차순으로 저장하고 있다. (1, 2, 3 혹은 a, b, c , ㄱ, ㄴ, ㄷ 등) 그래서 특정 문자열로 시작하는 데이터의 주소값은 알고 있지만 그렇지 않은 경우에는 모든 테이블을 직접 하나하나 찾아가면서 조건에 맞는 문자열을 검색한다.
우리가 검색 기능을 구현할 때는 특정 문자열을 포함하는지에 대해 필터링하기 때문에 필연적으로 풀스캔이 될 수 밖에 없다.
결론
레코드와 테이블이 많아지면 LIKE 쿼리로는 성능상 한계가 있을 수 밖에 없다. 그러나 자동완성은 다를 수 있다. 자동 완성은 접두사만을 검색하기 때문에 LIKE로도 충분히 성능을 고려하지 않고 검색할 수 있다고 생각한다.
보충
함수 기반 인덱스나 부분 인덱스라는 대체제가 LIKE의 성능 자체를 개선시킬 수 있다고 한다.
Full Text Search
다음으로 생각해볼 수 있는 것은 전문 검색(full text search, 편의상 풀텍스트서치 라고 부르겠다.)이다.
풀 텍스트 서치를 위해 MySQL에서는 full text index라는 기능을 운영한다. 이것은 InnoDB나 MyISAM에서 지원하는데, InnoDB는 8버전 이후로 디폴트로 설정된 스토리지 엔진이고 MyISAM은 5버전에서 디폴트로 설정된 스토리지 엔진이다. 여튼 Full Text Index를 학습해보자. mysql 공식 문서를 활용할 것인데, InnoDB를 기반으로 작성되어 있다.
Full Text Index
풀텍스트 인덱스는 텍스트 기반 컬럼을 대상으로 생성할 수 있다. VARCHAR, CHAR, TEXT 컬럼들을 대상으로 쿼리의 속도를 높이기 위해 사용된다. CREATE TABLE 문의 일부로 정의되거나 ALTER TABLE, CREATE INDEX를 활용하여 기존 테이블에서 추가할 수 있다. 풀 텍스트 서치는 MATCH() … AGAINST 구문을 사용하여 수행된다.
Full Text Index Design
InnoDB의 풀 텍스트 인덱스는 역색인 디자인(이하 역인덱스)으로 이루어져 있다. 역인덱스는 단어들의 리스트, 그리고 각각의 단어가 어떤 문서에서 나타났는지에 대한 리스트를 저장하고 있다. proximity 검색(근접성 검색, 단어 사이가 물리적으로 얼마나 근접한가에 대한 기준)을 지원하기 위해서 단어 각각의 위치 정보 또한 byte offset으로 저장되어 있다.
왼쪽은 우리가 일반적으로 사용하는 인덱스이며, docID와 내용을 매핑한다. 그래서 1번 문서로 유럽에 접근하고, 6번 문서로 퀘벡에 접근할 수 있다.
역인덱스는 오히려 유럽이라는 단어를 통해서 연관된 문서인 1번 문서, 2번 문서, 7번 문서를 찾아가는 인덱스의 역할이 반전되어 있는 것을 말한다.
그래서 역인덱스가 왜 필요한데? 라고 말한다면 다음의 예시를 살펴보자. 위의 경우에서 확장하여 문서 개수가 2천만개, 1억개가 있다고 가정하자. 그 때 누군가 퀘벡에 관련된 모든 정보를 확인하고 싶어한다. 그러면 2천만개, 1억개의 데이터를 모두 살펴볼 것인가?
역인덱스를 활용해서 퀘벡에 대한 문서가 몇 번인지 정리해두었다면, 훨씬 쉽게 정보를 찾을 수 있다. 즉, 역인덱스는 검색에서 매우 강력한 퍼포먼스를 낼 수 있는 자료구조이다.
InnoDB Full-Text Index Tables
InnoDB 풀텍스트 인덱스가 생성되었을 때, 인덱스 테이블의 집합이 생성된다. 그 예시는 다음과 같다.
먼저 TABLE을 생성해보자.
CREATE TABLE opening_lines (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
opening_line TEXT(500),
author VARCHAR(200),
title VARCHAR(200),
FULLTEXT idx (opening_line)
) ENGINE=InnoDB;
CREATE 쿼리가 성공적으로 끝나면 이제 인덱스 테이블 집합을 확인해보자.
SELECT table_id, name, space
FROM INFORMATION_SCHEMA.INNODB_TABLES
WHERE name LIKE '"현재 사용하고 있는 데이터베이스 이름"/%';
아래에서부터 index라고 이름이 붙은 테이블을 살펴보자. 이 여섯 개의 인덱스 테이블은 역인덱스를 구성하고 있고, 보조 색인 테이블이라고 한다. 입력 중인 문서가 토큰화되면 각각의 단어들(토큰이라고 부르는)은 위치 정보, 연관된 DOC_ID와 함께 인덱스 테이블에 삽입된다. 이 단어들은 첫 글자의 문자 집합 정렬 가중치(character set sort weight)에 따라 6개의 인덱스 테이블에 정렬되어 분산 저장된다. 예를 들어 ‘ㄱ’으로 시작하는 단어는 1번 테이블, ‘ㄴ’으로 시작하는 단어는 2번 테이블과 같은 형식이다. (이렇게 동작하는 이유는 여러 개의 스레드가 동시에 다른 테이블에서 작업할 수 있도록 하기 위함이다.)
역인덱스는 병렬 인덱스 생성을 지원하기 위해 6개의 보조 인덱스 테이블로 분할된다. 기본적으로 두 개의 스레드가 단어들과 관련된 데이터들을 토큰화하고, 정렬하고, 인덱스 테이블에 삽입한다. 이 작업을 수행하는 스레드의 개수는 innodb_ft_sort_pll_degree 변수에서 관리된다. 대용량 테이블에 풀텍스트 인덱스를 생성할 때는 스레드 개수를 늘리면 좋다.
보조 인덱스 테이블 이름들은 fts_ 의 접두사와 index_# 의 접미사로 이루어져 있다. 각각의 보조 인덱스 테이블은 인덱싱된 테이블의 table_id와 일치하는 보조 인덱스 테이블 이름의 16진수 값을 통해 인덱싱된 테이블과 연결된다.
이해하기 쉽도록 예시를 확인해보자. fti (각자 생성한 데이터베이스를 확인하자, 이건 내 경우의 예시이다.) 데이터베이스의 opening_lines 테이블을 확인해보면 table_id 는 1174이다. 이 값의 16진수 값은 0x496 이다.
또한, 보조 인덱스 테이블의 이름들은 풀텍스트 인덱스의 index_id 를 나타내는 16진수 값을 포함하고 있다. 예를 들어, fti/fts_0x496_0x16c_index_1 이라는 보조 인덱스 테이블에서 16진수 값인 16c는 index_id 를 나타내고, 364라는 값이다.
기본 테이블이 file-per-table 테이블스페이스에 생성된 경우, 인덱스 테이블은 자체 테이블스페이스에 저장된다. 그렇지 않으면 인덱스 테이블은 인덱싱된 테이블이 있는 테이블스페이스에 저장된다.
💡 file-per-table이 뭔데? (공식 문서 번역)
"file-per-table"은 innodb_file_per_table 옵션으로 제어되는 설정의 일반적인 이름으로, InnoDB 파일 저장, 기능 가용성 및 I/O 특성에 영향을 주는 중요한 구성 옵션입니다. MySQL 5.6.7부터는 innodb_file_per_table이 기본적으로 활성화되어 있습니다.
innodb_file_per_table 옵션이 활성화되면, 테이블을 시스템 테이블스페이스의 공유 ibdata 파일이 아닌 자체 .ibd 파일에 생성할 수 있습니다. 테이블 데이터가 개별 .ibd 파일에 저장될 때, 데이터 압축과 같은 기능에 필요한 행 형식을 더 유연하게 선택할 수 있습니다. TRUNCATE TABLE 작업도 더 빠르며, 회수된 공간은 InnoDB용으로 예약된 상태로 남아있지 않고 운영 체제에서 사용할 수 있습니다.
MySQL Enterprise Backup 제품은 각자의 파일에 있는 테이블에 대해 더 유연합니다. 예를 들어, 테이블이 별도의 파일에 있는 경우에만 백업에서 제외할 수 있습니다. 따라서 이 설정은 덜 자주 백업하거나 다른 일정으로 백업하는 테이블에 적합합니다.
관련 항목: 압축된 행 형식, 압축, 파일 형식, .ibd 파일, ibdata 파일, innodb_file_per_table, MySQL Enterprise Backup, 행 형식, 시스템 테이블스페이스.
앞의 예에서 보여준 다른 인덱스 테이블은 공통 인덱스 테이블(common index table)이라 하며, 삭제 처리 및 풀텍스트 인덱스의 내부 상태 저장에 사용된다. 각 풀텍스트 인덱스마다 생성되는 역인덱스 테이블과 달리, 이 테이블 집합은 특정 테이블에생성된 모든 풀텍스트 인덱스에 공통적으로 적용된다.
쉽게 예시를 들어보면, 한 테이블에서 title과 content라는 text type의 컬럼이 있다고 가정해보자. title에 풀텍스트 인덱스를 적용하고 content에 적용하면 역인덱스 테이블은 각각 생성되나 공통 인덱스 테이블은 두 가지 컬럼이 공유하고 있다고 생각하면 된다.
풀텍스트 인덱스가 삭제되더라도 공통 인덱스 테이블은 유지된다. 풀텍스트 인덱스가 삭제되면, 해당 인덱스에 대해 생성된 FTS_DOC_ID 컬럼은 유지된다. FTS_DOC_ID 컬럼을 삭제하려면 이전에 인덱싱된 테이블을 다시 작성하기 때문이다. 이 FTS_DOC_ID 컬럼을 관리하기 위해 공통 인덱스 테이블이 필요하다.
이제 공통 인덱스 테이블이 어떤 역할을 하는지 살펴보자.
fts_*_delted 와 fts_*_deleted_cache
삭제되었지만 풀텍스트 인덱스에서 데이터가 아직 제거되지 않은 문서의 DOC_ID를 포함한다. cache가 붙은 녀석은 deleted의 인메모리 버전이다.
fts_*_being_deleted 와 fts_*_being_deleted_cache
삭제된 문서의 DOC_ID를 포함하며, 해당 데이터는 풀텍스트 인덱스에서 현재 제거되고 있는 데이터들이다. cache는 마찬가지로 deleted의 인메모리버전이다.
fts_*_config이 테이블의 데이터를 보려면 INFORMATION SCHEMA의 INNODB_FT_CONFIG 테이블을 조회하면 된다.
풀텍스트 인덱스의 내부 상태에 대한 정보를 저장한다. 가장 중요한 것은 FTS_SYNCED_DOC_ID 를 저장하는데, 이는 파싱되어 디스크에 플러시된 문서를 식별한다. 충돌 복구 시, FTS_SYNCED_DOC_ID 값을 이용하여 디스크에 플러시되지 않은 문서를 식별하고 해당 문서를 다시 파싱하여 풀텍스트 인덱스 캐시에 다시 추가할 수 있다.
InnoDB Full-Text Index Cache
문서가 삽입되면 토큰화되고, 개별 단어와 관련 데이터가 풀텍스트 인덱스에 삽입된다. 이 프로세스는 작은 길이의 문서라도 보조 인덱스 테이블에 여러 개의 작은 삽입 작업을 발생시켜서, 테이블에 대한 동시 접근의 race condition이 발생할 수 있다. 이런 병목 현상을 방지하기 위해 InnoDB는 풀텍스트 인덱스 캐시를 사용하여 최근에 삽입된 로우에 대한 인덱스 테이블 삽입을 임시로 캐싱한다. 이 메모리 내 캐시 구조는, 캐시가 가득 찰 때까지 삽입을 보관하다가 디스크(보조 인덱스 테이블)에 일괄적으로 플러시한다. INFORMATION SCHEMA의 INNODB_FT_INDEX_CACHE 테이블을 통해 최근 삽입된 로우에 대한 토큰 데이터를 확인할 수 있다.
캐싱 및 배치 플러싱(batch flushing) 동작은 보조 인덱스 테이블에 대한 빈번한 업데이트를 방지하여, 상비과 업데이트가 많이 발생하는 시간대에 동시 접근 문제가 발생할 가능성을 줄인다. 이 배치 처리 기술은 동일 단어에 대한 중복 삽입을 방지하고 항목을 최소화한다. 각 단어를 개별적으로 플러시하는 대신, 같은 단어에 대한 삽입들은 병합되어 단일 항목으로 디스크에 플러시된다. 이는 보조 인덱스 테이블을 가능한 작게 유지하면서 삽입 효율성을 향상시킨다.
innodb_ft_cache_size 변수는 풀텍스트 인덱스 캐시 크기(테이블별)를 구성하는 데 사용되며, 이 값은 풀텍스트 인덱스 캐시가 얼마나 자주 플러시되는지 영향을 미친다. (크면 가득 차는데 오래 걸리겠지?) 또한 innodb_ft_total_cache_size 변수를 사용하여 주어진 인스턴스 내 모든 테이블에 대한 전역 풀텍스트 인덱스 캐시 크기 제한을 정의할 수도 있다. 여기서 인스턴스란 현재 돌아가고 있는 MySQL 서버의 인스턴스, 애플리케이션을 의미한다.
풀텍스트 인덱스 캐시는 보조 인덱스 테이블과 동일한 정보를 저장한다. 그러나 풀텍스트 인덱스 캐시는 최근에 삽입된 로우에 대한 토큰화된 데이터만 캐싱한다. 이미 디스크(보조 인덱스 테이블)에 플러시된 데이터는 쿼리될 때 풀텍스트 인덱스 캐시로 다시 가져오지 않는다. 보조 인덱스 테이블의 데이터는 직접 쿼리되며, 보조 인덱스 테이블의 결과는 반환되기 전에 풀텍스트 인덱스 캐시의 결과와 병합된다.
InnoDB Full-Text Index DOC_ID and FTS_DOC_ID Column
InnoDB는 풀텍스트 인덱스의 단어들을 해당 단어가 나타나는 문서 레코드에 매핑하기 위해서 DOC_ID 라고 하는 고유한 문서 식별자를 사용한다. 이 매핑 과정은 인덱싱된 테이블에 FTS_DOC_ID 컬럼을 필요로 한다. 만약 FTS_DOC_ID 가 정의되어 있지 않다면 InnoDB는 풀텍스트 인덱스가 생성될 때 자동으로 숨겨진 FTS_DOC_ID 컬럼을 추가한다. 다음의 예제를 확인해보자.
CREATE TABLE opening_lines (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
opening_line TEXT(500),
author VARCHAR(200),
title VARCHAR(200)
) ENGINE=InnoDB;
이 테이블은 FTS_DOC_ID 컬럼을 포함하고 있지 않다. CREATE FULLTEXT INDEX 구문을 활용해서 테이블에 풀텍스트 인덱스를 생성하면 InnoDB가 FTS_DOC_ID 컬럼을 추가하기 위해 테이블을 재구성 중이라는 경고가 반환된다.
CREATE FULLTEXT INDEX idx ON opening_lines(opening_line);
FTS_DOC_ID 가 없는 테이블에 대해 ALTER TABLE을 적용하면 마찬가지의 에러가 발생한다. CREATE TABLE 시에 풀텍스트 인덱스를 생성하면, FTS_DOC_ID 컬럼을 명시하지 않아도 InnoDB가 숨겨진 컬럼을 경고 없이 생성해준다.
CREATE TABLE 시점에 FTS_DOC_ID 컬럼을 정의하는 것은 이미 데이터가 저장된 테이블에 풀텍스트 인덱스를 생성하는 것보다 비용이 적게 든다. 데이터를 로드하기 전에 테이블에 FTS_DOC_ID 컬럼을 정의하면 새 컬럼을 추가하기 ㅜ이해 테이블과 인덱스를 재구축할 필요가 없다. CREATE FULLTEXT INDEX 성능에 관심이 없다면, FTS_DOC_ID 컬럼을 생략하여 InnoDB가 대신 생성하도록 하면 된다.
InnoDB는 숨겨진 FTS_DOC_ID 컬럼과 함께 FTS_DOC_ID 컬럼에 대한 고유 인덱스(FTS_DOC_ID_INDEX)를 생성한다. 자체 FTS_DOC_ID 컬럼을 생성하려면, 다음 예제와 같이 컬럼을 BIGINT UNSIGNED NOT NULL로 정의하고 FTS_DOC_ID(모두 대문자)로 이름을 지정해야 한다.
CREATE TABLE opening_lines (
FTS_DOC_ID BIGINT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
opening_line TEXT(500),
author VARCHAR(200),
title VARCHAR(200)
) ENGINE=InnoDB;
이렇게 FTS_DOC_ID 를 직접 정의하는 경우, 빈 값이나 중복 값이 발생하지 않도록 컬럼을 직접 관리해야 한다. 그리고 해당 값은 재사용할 수 없으므로 계속 증가해야 한다.
선택적으로, FTS_DOC_ID 컬럼에 대한 유니크 인덱스인 FTS_DOC_ID_INDEX 를 생성할 수 있다. 만약 직접 생성하지 않는다면 InnoDB가 자동으로 생성해준다.
CF) InnoDB SQL 파서는 내림차순 인덱스를 사용하지 않으므로 FTS_DOC_ID_INDEX를 내림차순 인덱스로 정의할 수 없다.
InnoDB 엔진은 풀텍스트 인덱스의 내부 관리를 위해 FTS_DOC_ID 값에 대한 특별한 규칙을 가지고 있다. 시스템이 새로운 FTS_DOC_ID 값을 생성할 때, 가장 최근에 사용된 FTS_DOC_ID 값에서 최대 65535만큼 차이가 날 수 있다.
이해하기 쉽도록 예시를 보자.
테이블에 마지막으로 할당된 값이 1000이라 가정하자.
시스템이 충돌하고 복구된 후, 혹은 특정 관리 작업 이후에 새 문서를 삽입하는 상황이 생겼다.
이럴 때, 새 문서에 할당되는 FTS_DOC_ID 값은 최대 1000 + 65535인 66535까지 건너뛸 수 있다.
CF) 이 간격은 내부적으로 일관성을 유지하고 복구 작업 중에 잠재적인 충돌을 방지하기 위한 것이다. 따라서 FTS_DOC_ID 값이 순차적이지 않고 큰 간격이 있더라도 이는 정상적인 동작이다. 특히 대량의 삽입과 삭제가 혼합된 작업을 수행할 때 이러한 현상이 자주 발생할 수 있습니다.
테이블 재구축을 방지하기 위해 전체 텍스트 인덱스를 삭제할 때 FTS_DOC_ID 열은 삭제되지 않는다.
InnoDB Full-Text Index Deletion Handling
풀텍스트 인덱스 컬럼이 있는 레코드를 삭제하면 보조 인덱스 테이블에서 수많은 작은 삭제가 발생할 수 있다. 삽입과 마찬가지로 이런 부분이 동시 접근을 일으키고 병목 현상을 발생시킬 수 있다. 이 문제를 방지하기 위해 인덱싱된 테이블에서 레코드가 삭제될 때마다 삭제된 문서의 DOC_ID 는 특별한 FTS_*_DELETED 테이블에 기록된다. 그리고 인덱싱된 레코드는 풀텍스트 인덱스에 남아있게 된다. 쿼리 결과를 반환하기 전에 FTS_*_DELETED 테이블의 정보를 사용하여 삭제된 DOC_ID 를 필터링한다. (소프트 삭제의 개념이 적용되었다고 이해하면 될 것 같다.) 소프트 삭제이므로 삭제가 빠르고 비용이 작다는 장점이 있다. 단점으로는 인덱스 크기가 즉시 줄어들지 않는다.
삭제된 레코드에 대한 풀텍스트 인덱스 항목을 제거하려면 innodb_optimize_fulltext_only=ON 상태에서 인덱싱된 테이블에 OPTIMIZE TABLE을 실행시켜서 풀텍스트 인덱스를 재구축 해야 한다.
InnoDB Full-Text Index Transaction Handling
InnoDB 풀텍스트 인덱스는 캐싱과 배치 처리 동작으로 인해 특별한 트랜잭션 처리 특성을 가진다. 풀텍스트 인덱스에 대한 업데이트와 삽입은 트랜잭션 커밋 시점에 처리되므로, 풀텍스트 검색은 커밋된 데이터만 볼 수 있다. 풀텍스트 검색은 삽입된 행이 커밋된 후에만 결과를 반환하는데, 이것을 직접 확인해보자.
CREATE TABLE opening_lines (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
opening_line TEXT(500),
author VARCHAR(200),
title VARCHAR(200),
FULLTEXT idx (opening_line)
) ENGINE=InnoDB;
BEGIN;
INSERT INTO opening_lines(opening_line,author,title) VALUES
('Call me Ishmael.','Herman Melville','Moby-Dick'),
('A screaming comes across the sky.','Thomas Pynchon','Gravity\'s Rainbow'),
('I am an invisible man.','Ralph Ellison','Invisible Man'),
('Where now? Who now? When now?','Samuel Beckett','The Unnamable'),
('It was love at first sight.','Joseph Heller','Catch-22'),
('All this happened, more or less.','Kurt Vonnegut','Slaughterhouse-Five'),
('Mrs. Dalloway said she would buy the flowers herself.','Virginia Woolf','Mrs. Dalloway'),
('It was a pleasure to burn.','Ray Bradbury','Fahrenheit 451');
SELECT COUNT(*) FROM opening_lines WHERE MATCH(opening_line) AGAINST('Ishmael');
COMMIT;
SELECT COUNT(*) FROM opening_lines
WHERE MATCH(opening_line) AGAINST('Ishmael');
단어 정리
테이블스페이스
MySQL에서 테이블 스페이스(Tablespace)는 데이터와 인덱스를 물리적으로 저장하는 데 사용되는 공간입니다. 테이블 스페이스는 데이터베이스 내에서 테이블과 인덱스를 관리하고 저장하는 데 사용됩니다. 이는 데이터 파일로 구성되며, 각각의 데이터 파일은 특정 테이블 또는 인덱스에 해당합니다.