이 기사는 MYSQL 및 InnoDB 스토리지 엔진의 고급 아키텍처에 대한 지식을 제공하는 데 도움이 되기를 바랍니다.
MySQL 기본 아키텍처 다이어그램
일반적으로 MySQL은 서버 계층과 스토리지 엔진 계층의 두 부분으로 나눌 수 있습니다.
서버 계층에는 대부분의 MySQL 핵심 서비스 기능과 모든 내장 기능(예: 날짜, 시간, 수학 및 암호화 기능 등)을 다루는 커넥터, 쿼리 캐시, 분석기, 최적화 프로그램, 실행기 등이 포함되어 있습니다. ), 모든 교차 - 저장 프로시저, 트리거, 뷰 등과 같은 스토리지 엔진의 기능이 이 계층에서 구현됩니다.
Connector
커넥터는 데이터베이스에 연결할 때 사용하는 것입니다. 클라이언트와의 연결 설정, 권한 획득, 연결 유지 및 관리를 담당합니다.
명령어: mysql -h$ip -P$port -u$user -p, Enter를 누르고 비밀번호를 입력해도 됩니다. -p 뒤에도 비밀번호를 입력할 수 있지만 비밀번호 유출의 위험이 있습니다.
show processlist를 사용하면 연결 상태를 확인할 수 있습니다. 명령 열에 연결이 유휴 상태임을 나타내는 절전 모드가 있습니다.
유휴 연결은 기본적으로 8시간 동안 연결이 끊어지며 wait_timeout 매개변수로 구성할 수 있습니다.
데이터베이스에서 긴 연결은 연결이 성공한 후에도 클라이언트가 계속 요청하면 항상 동일한 연결이 사용된다는 의미입니다. 짧은 연결은 몇 번의 쿼리가 실행된 후 연결이 끊어지고 다음 쿼리를 위해 새 연결이 다시 설정되는 것을 의미합니다.
연결을 설정하면 리소스가 소모되므로 최대한 긴 연결을 사용하는 것이 좋습니다. 그러나 긴 연결을 사용한 후에는 MySQL이 실행 중에 일시적으로 사용하는 메모리가 매우 빠르게 증가하기 때문입니다. 연결 개체에서. 이러한 리소스는 연결이 끊어지면 해제됩니다. 따라서 긴 연결이 누적되면 너무 많은 메모리를 차지하여 시스템(OOM)에 의해 강제로 종료되는 현상으로 판단하면 MySQL이 비정상적으로 다시 시작됩니다.
해결책:
긴 연결을 정기적으로 끊습니다. 일정 기간 사용 후 또는 프로그램에서 메모리를 차지하는 큰 쿼리가 실행된 것으로 판단한 후 연결이 끊어진 후 쿼리가 필요했다가 다시 연결됩니다.
MySQL 5.7 이상을 사용하는 경우 상대적으로 큰 작업을 수행할 때마다 mysql_reset_connection을 실행하여 연결 리소스를 다시 초기화할 수 있습니다. 이 프로세스에는 재연결 및 권한 확인이 필요하지 않지만 방금 생성된 상태로 연결을 복원합니다.
쿼리 캐시
쿼리 캐시는 이전에 실행된 명령문과 그 결과를 키-값 쌍의 형태로 메모리에 캐시합니다. 키는 쿼리문이고, 값은 쿼리 결과입니다. 쿼리가 이 캐시에서 직접 키를 찾을 수 있으면 값이 클라이언트에 직접 반환됩니다.
MYSQL8에서는 잦은 쿼리 캐시 실패로 인해 적중률이 낮습니다.
Analyzer
분석기는 먼저 "어휘 분석"을 수행하여 내부 문자열이 무엇인지, 무엇을 나타내는지 식별합니다. 그런 다음 입력한 SQL 문이 MySQL 구문을 만족하는지 확인하기 위해 "구문 분석"을 수행해야 합니다.
Optimizer
Executor
스토리지 엔진 계층은 데이터 저장 및 검색을 담당합니다. 아키텍처 모델은 플러그인이며 InnoDB, MyISAM 및 Memory와 같은 여러 스토리지 엔진을 지원합니다. 현재 가장 일반적으로 사용되는 스토리지 엔진은 MySQL 버전 5.5.5부터 기본 스토리지 엔진이 된 InnoDB입니다.
A Select 문 실행 프로세스
위 그림은 InnoDB 스토리지 엔진을 예로 들어 처리 과정은 다음과 같습니다.
사용자가 Tomcat에 요청을 보내고 연결됩니다. Tomcat 링크 풀과 mysql 연결 풀을 통해 설정됩니다. 그런 다음 연결을 통해 MySQL에 SQL 문을 보내고
MySQL에는 별도의 수신 스레드가 있고 요청 데이터를 읽고 연결에서 요청된 SQL 문을 가져옵니다.
예를 들어, 실행자는 먼저 스토리지 엔진의 인터페이스를 호출하여 "users" 테이블에 있는 데이터의 첫 번째 행을 얻은 다음 이 데이터의 "id" 필드 값이 무엇과 같은지 확인할 수 있습니다. 값이 아닌 경우 계속해서 스토리지 엔진 인터페이스를 호출하여 "users" 테이블의 다음 데이터 행을 얻습니다. 위의 아이디어를 바탕으로 실행자는 최적화 프로그램에서 생성된 실행 계획 세트를 사용한 다음 스토리지 엔진의 다양한 인터페이스를 지속적으로 호출하여 대략적으로 지속적으로 업데이트하거나 추출하는 SQL 문의 실행 계획을 완료합니다. 데이터가 나옵니다.
여기에는 몇 가지 질문이 있습니다.
MySQL 드라이버가 정확히 무엇입니까?
Java를 예로 들어보겠습니다. Java 시스템에서 MySQL 데이터베이스에 액세스하려면 시스템 종속성에 MySQL 드라이버를 추가해야 합니다. 예를 들어 Maven에서는
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.46</version> </dependency>
를 추가해야 합니다. MySQL 드라이버란 무엇입니까? 실제로 L 드라이버는 하위 계층의 데이터베이스와 네트워크 연결을 설정합니다. 일단 네트워크 연결이 이루어지면 데이터베이스 서버에 요청을 보낼 수 있습니다. 아래와 같이 언어로 작성된 시스템이 MySQL 드라이버를 통해 데이터베이스에 접근하게 해주세요
데이터베이스 연결 풀은 어떤 용도로 사용되나요?
웹 서비스가 Java로 개발되고 Tomcat에 배포된다고 가정해 보겠습니다. Tomcat은 여러 스레드로 동시에 요청을 처리할 수 있으므로 첫 번째로 하나의 데이터베이스 연결만 만드는 것이 불가능하다는 것입니다(하나의 연결을 얻기 위해 여러 요청을 하는 것은 많은 작업입니다). 무능한).
두 번째로, 요청마다 데이터베이스 연결이 생성된다면 이것도 매우 좋지 않습니다. 매번 데이터베이스 연결을 설정하고, SQL 문을 실행하는 것이 쉽지 않기 때문입니다. 그런 다음 데이터베이스를 삭제하면 성능 문제가 발생하고 생성 및 파괴가 자주 발생합니다.
따라서 일반적으로 데이터베이스 연결 풀을 사용합니다. 즉, 풀에서 여러 데이터베이스 연결을 유지하고 여러 스레드가 서로 다른 데이터베이스 연결을 사용하여 SQL 문을 실행하도록 한 다음 SQL 문을 실행한 후에 데이터베이스 연결을 파괴하지 마십시오. 대신 연결이 다시 풀에 들어가고 나중에 다시 사용할 수 있습니다. 이러한 데이터베이스 연결 풀 메커니즘을 기반으로 여러 스레드가 동시에 여러 데이터베이스 연결을 사용하여 SQL 문을 실행하는 문제를 해결할 수 있으며, 사용 후 데이터베이스 연결이 끊어지는 문제를 피할 수 있습니다.
MySQL 데이터베이스의 연결 풀은 어떤 용도로 사용되나요?
MySQL 데이터베이스의 연결 풀은 Java 애플리케이션 연결 풀과 동일한 기능을 가지며 둘 다 연결을 재사용하는 역할을 합니다.
InnoDB 스토리지 엔진
InnoDB 아키텍처 개요
그림에서 볼 수 있듯이 InnoDB 스토리지 엔진은 메모리 풀, 백그라운드 스레드, 디스크 파일의 세 부분으로 구성됩니다.
또 다른 핵심 사항을 강조하기 위한 것 그림:
InnoDB 스토리지 엔진 파트 1: 메모리 구조
버퍼 풀
InnoDB 스토리지 엔진은 디스크 스토리지를 기반으로 하며 페이지 단위로 레코드를 관리합니다. CPU 속도와 디스크 속도 사이의 차이로 인해 디스크 기반 데이터베이스 시스템은 데이터베이스의 전반적인 성능을 향상시키기 위해 버퍼 풀 레코드를 사용하는 경우가 많습니다.
데이터베이스에서 읽기 작업을 수행할 때 디스크에서 읽은 페이지는 다음에 동일한 페이지를 읽을 때 먼저 해당 페이지가 버퍼 풀에 있는지 확인합니다. 버퍼 풀에 있으면 페이지를 버퍼 풀에 적중하고 페이지를 직접 읽는다고 합니다. 그렇지 않으면 디스크에 있는 페이지를 읽습니다.
데이터베이스의 페이지 수정 작업의 경우 버퍼 풀의 페이지가 먼저 수정된 후 특정 빈도로 디스크로 새로 고쳐집니다. 버퍼 풀의 페이지를 다시 디스크로 새로 고치는 작업은 트리거되지 않습니다. 대신 페이지가 업데이트될 때마다 CheckPoint라는 메커니즘을 통해 디스크로 다시 플러시됩니다. 따라서 버퍼 풀의 크기는 데이터베이스의 전체 성능에 직접적인 영향을 미칩니다. 이는 구성 매개변수 innodb_buffer_pool_size를 통해 설정할 수 있으며, 데이터베이스가 16코어 32G인 경우에는 여전히 약간 작은 값입니다. 그런 다음 버퍼 풀에 2GB의 메모리를 할당할 수 있습니다.
버퍼 풀은 무한하지 않기 때문에 디스크의 데이터 페이지가 지속적으로 버퍼 풀에 로드되므로 버퍼 풀은 항상 소모됩니다. 이때 일부 캐시 페이지만 제거할 수 있습니다. 특히 최신 LRU(최소 사용 알고리즘)는 새로운 LRU 연결 목록을 도입하여 최근에 가장 적게 사용된 캐시 페이지를 알 수 있습니다. 그런 다음 캐시 페이지를 비워야 할 경우 이를 플러시합니다. 이때 LRU 목록에서 가장 최근에 사용된 캐시 페이지를 선택하여 제거할 수 있습니다.
버퍼 풀에 캐시되는 데이터 페이지 유형에는 인덱스 페이지, 데이터 페이지, 실행 취소 페이지, 삽입 버퍼, 적응형 해시 인덱스, 잠금 정보 및 InnoDB에 저장된 데이터 사전 정보가 포함됩니다.
데이터 페이지 및 인덱스 페이지
페이지(Page)는 Innodb 스토리지의 가장 기본적인 구조이며 Innodb 디스크 관리의 가장 작은 단위입니다. 데이터베이스와 관련된 모든 내용이 페이지 구조에 저장됩니다. 페이지는 여러 유형으로 나뉘며, 데이터 페이지와 인덱스 페이지가 가장 중요한 두 가지 유형입니다.
Insert Buffer
InnoDB 엔진에서 삽입 작업을 수행할 때는 일반적으로 더 높은 삽입 성능을 얻기 위해 기본 키 순서대로 삽입해야 합니다. 테이블에 비클러스터형 비고유 인덱스가 있는 경우 삽입 시 데이터 페이지는 기본 키에 따라 순서대로 저장되지만 이 경우 비클러스터형 인덱스 리프 노드 삽입은 더 이상 순차적이지 않습니다. 비클러스터형 인덱스 페이지에 대한 개별적인 액세스는 임의 읽기로 인해 삽입 작업의 성능 저하를 초래합니다.
그래서 InnoDB 스토리지 엔진은 비클러스터형 인덱스의 삽입 또는 업데이트 작업을 위해 매번 인덱스 페이지에 직접 삽입하는 대신 삽입된 비클러스터형 인덱스 페이지가 버퍼에 있는지 먼저 확인합니다. pool이 있으면 직접 삽입되고, 없으면 먼저 Insert Buffer 객체에 들어가게 되는데, 이는 부정행위로 보입니다. 데이터베이스의 비클러스터형 인덱스가 리프 노드에 삽입되었지만 실제로는 다른 위치에 저장되어 있을 뿐입니다. 그런 다음 Insert Buffer와 보조 인덱스 페이지의 하위 노드의 병합 작업이 특정 빈도와 상황으로 수행됩니다. 이때 여러 개의 삽입이 일반적으로 하나의 작업으로 병합될 수 있습니다(하나의 인덱스 페이지에 있기 때문). 이는 비클러스터형 인덱스 삽입의 성능을 크게 향상시킵니다.
단, Insert Buffer를 사용하려면 다음 두 가지 조건을 동시에 충족해야 합니다.
인덱스가 보조 인덱스(secondary index)입니다.
인덱스가 고유하지 않습니다(unique).
위의 두 가지 조건이 충족되면 InnoDB 스토리지 엔진은 삽입 작업 성능을 향상시킬 수 있는 삽입 버퍼를 사용합니다. 그러나 응용 프로그램이 고유하지 않은 비클러스터형 인덱스, 즉 삽입 버퍼를 사용하는 많은 삽입 작업을 수행하는 상황을 생각해 보세요. 이때 MySQL 데이터베이스가 다운되면 실제 비클러스터형 인덱스에 병합되지 않은 삽입 버퍼가 대량으로 존재하게 됩니다.
그래서 이때의 회복은 오랜 시간이 걸릴 수도 있고, 극단적인 경우에는 몇 시간이 걸릴 수도 있습니다. 버퍼를 삽입할 때 데이터베이스는 삽입된 레코드의 고유성을 확인하기 위해 인덱스 페이지를 조회하지 않으므로 보조 인덱스는 고유할 수 없습니다. 검색하면 확실히 개별 읽기가 발생하므로 버퍼 삽입이 의미를 잃게 됩니다.
SHOW ENGINE INNODB STATUS
seg size 명령을 통해 삽입 버퍼 정보를 볼 수 있습니다. 현재 삽입 버퍼 크기는 11336×16KB로 약 177MB입니다. 목록 크기는 병합된 레코드 페이지 수를 나타냅니다. 굵게 표시된 두 번째 줄은 삽입 성능이 향상되었음을 보여주기 때문에 사용자가 실제로 관심을 갖는 부분일 수 있습니다. Inserts는 삽입된 레코드 수를 나타내고, merged는 병합된 삽입된 레코드 수를 나타내며, merges는 실제 페이지 읽기 수인 병합 수를 나타냅니다. merges: merged recs는 약 1:3입니다. 이는 삽입 버퍼가 비클러스터형 인덱스 페이지에 대한 개별 IO 논리 요청을 약 2/3로 줄인다는 의미입니다.
앞서 언급했듯이 현재 버퍼 삽입에 문제가 있습니다. 쓰기 집약적인 조건에서 삽입 버퍼링은 너무 많은 버퍼 풀 메모리(innodb 버퍼 풀)를 차지합니다. 기본적으로 버퍼의 최대 1/2을 차지할 수 있습니다. 풀. InnoDB 스토리지 엔진 소스 코드 중 insert 버퍼에 대한 초기화 작업은 다음과 같습니다.
Change Buffer
InnoDB는 1.0.x 버전부터 Change Buffer를 도입했는데, 이는 Insert Buffer의 업그레이드 버전이라고 볼 수 있습니다. , InnodB 스토리지 엔진은 DML 작업인 INSERT, DELETE, UPDATE를 버퍼링할 수 있습니다. Insert Buffer, Delete Buffer, Purge buffer 물론 이전의 Insert Buffer와 마찬가지로 Change Buffer의 적용 가능한 개체도 여전히 고유하지 않습니다. 보조 인덱스.
레코드에 대한 UPDATE 작업은 두 가지 프로세스로 나눌 수 있습니다.
레코드를 삭제된 것으로 표시하고
실제로 레코드를 삭제합니다.
따라서 버퍼 삭제는 UPDATE 작업의 첫 번째 프로세스에 해당합니다. 이는 해당 레코드에 삭제 표시를 하는 것입니다. PurgeBuffer는 실제 삭제를 기록하려는 UPDATE 작업의 두 번째 프로세스에 해당합니다. 동시에 InnoDB 스토리지 엔진은 다양한 버퍼 옵션을 활성화하는 데 사용되는 innodb_change_buffering 매개변수를 제공합니다. 이 매개변수의 선택적 값은 삽입, 삭제, 제거, 변경, 모두, 없음입니다. 삽입, 삭제 및 제거는 앞에서 설명한 세 가지 상황입니다. 변경은 삽입 및 삭제 활성화를 의미하고, 모두는 모두 활성화를 의미하며, 없음은 없음을 활성화한다는 의미입니다. 이 매개변수의 기본값은 all입니다.
从 InnoDB1.2.x版本开始,可以通过参数 innodb_change_buffer_max_size 来控制Change Buffer最大使用内存的数量:
mysql> show variables like 'innodb_change_buffer_max_size'; +-------------------------------+-------+ | Variable_name | Value | +-------------------------------+-------+ | innodb_change_buffer_max_size | 25 | +-------------------------------+-------+ 1 row in set (0.05 sec)
innodb_change_buffer_max_size 值默认为25,表示最多使用1/4的缓冲池内存空间。
而需要注意的是,该参数的最大有效值为50在 MySQL5.5版本中通过命令 SHOW ENGINE INNODB STATUS,可以观察到类似如下的内容:
可以看到这里显示了 merged operations和 discarded operation,并且下面具体显示 Change Buffer中每个操作的次数。 Insert 表示 Insert Buffer; delete mark表示 Delete Buffer; delete表示 Purge Buffer; discarded operations表示当 Change Buffer发生 merge时,表已经被删除,此时就无需再将记录合并(merge)到辅助索引中了。
自适应哈希索引
InnoDB 会根据访问的频率和模式,为热点页建立哈希索引,来提高查询效率。InnoDB 存储引擎会监控对表上各个索引页的查询,如果观察到建立哈希索引可以带来速度上的提升,则建立哈希索引,所以叫做自适应哈希索引。
自适应哈希索引通过缓冲池的B+树页构建而来,因此建立速度很快,而且不需要对整张数据表建立哈希索引。其有一个要求,即对这个页的连续访问模式必须一样的,也就是说其查询的条件必须完全一样,而且必须是连续的。
锁信息(lock info)
我们都知道,InnoDB 存储引擎会在行级别上对表数据进行上锁,不过 InnoDB 打开一张表,就增加一个对应的对象到数据字典。
数据字典
对数据库中的数据、库对象、表对象等的元信息的集合。在 MySQL 中,数据字典信息内容就包括表结构、数据库名或表名、字段的数据类型、视图、索引、表字段信息、存储过程、触发器等内容,MySQL INFORMATION_SCHEMA 库提供了对数据局元数据、统计信息、以及有关MySQL Server的访问信息(例如:数据库名或表名,字段的数据类型和访问权限等)。该库中保存的信息也可以称为MySQL的数据字典。
预读机制
MySQL的预读机制,就是当你从磁盘上加载一个数据页的时候,他可能会连带着把这个数据页相邻的其他数据页,也加载到缓存里去!
举个例子,假设现在有两个空闲缓存页,然后在加载一个数据页的时候,连带着把他的一个相邻的数据页也加载到缓存里去了,正好每个数据页放入一个空闲缓存页!
哪些情况下会触发MySQL的预读机制?
有一个参数是innodb_read_ahead_threshold,他的默认值是56,意思就是如果顺序的访问了一个区里的多个数据页,访问的数据页的数量超过了这个阈值,此时就会触发预读机制,把下一个相邻区中的所有数据页都加载到缓存里去。
如果Buffer Pool里缓存了一个区里的13个连续的数据页,而且这些数据页都是比较频繁会被访问的,此时就会直接触发预读机制,把这个区里的其他的数据页都加载到缓存里去这个机制是通过参数innodb_random_read_ahead来控制的,他默认是OFF,也就是这个规则是关闭的。
所以默认情况下,主要是第一个规则可能会触发预读机制,一下子把很多相邻区里的数据页加载到缓存里去。
预读机制的好处为了提升性能。假设你读取了数据页01到缓存页里去,那么接下来有可能会接着顺序读取数据页01相邻的数据页02到缓存页里去,这个时候,是不是可能在读取数据页02的时候要再次发起一次磁盘IO?
所以为了优化性能,MySQL才设计了预读机制,也就是说如果在一个区内,你顺序读取了好多数据页了,比如数据页01到数据页56都被你依次顺序读取了,MySQL会判断,你可能接着会继续顺序读取后面的数据页。那么此时就提前把后续的一大堆数据页(比如数据页57到数据页72)都读取到Buffer Pool里去。
缓冲池内存管理
这里需要了解三个链表(Free List、Flush List、LRU List),
Free List 디스크의 데이터 페이지와 캐시 페이지는 일대일 대응으로, 둘 다 16KB이며, 데이터 페이지 하나가 캐시 페이지 하나에 대응됩니다. 데이터베이스는 양방향 연결 목록 데이터 구조인 버퍼 풀에 대한 자유 연결 목록을 설계합니다. 이 자유 연결 목록에서 각 노드는 데이터 블록을 설명하는 자유 캐시 페이지의 주소입니다. 하나의 캐시 페이지가 비어 있는 한 그의 설명 데이터 블록은 이 무료 링크 목록에 저장됩니다. 데이터베이스가 처음 시작되면 모든 캐시 페이지는 데이터가 전혀 없는 빈 데이터베이스일 수 있으므로 이때 모든 캐시 페이지의 설명 데이터 블록이 이 무료 연결 목록에 추가됩니다. , 이 자유 링크 리스트에는 링크 리스트의 헤드 노드와 테일 노드를 참조하는 기본 노드가 있습니다. 또한 링크 리스트에 데이터 블록을 설명하는 노드 수, 즉 사용 가능한 캐시 페이지가 몇 개 있는지 저장합니다. .
Flush 목록은 Free List 연결 목록과 유사합니다. Flush 연결 목록의 본질은 캐시 페이지의 설명 데이터 블록에 두 개의 포인터를 사용하여 수정된 설명 데이터 블록을 사용하여 양방향 연결 목록을 구성하는 것입니다. 캐시 페이지. 수정된 모든 캐시 페이지에는 플러시 연결 목록에 설명 데이터 블록이 추가됩니다. 플러시는 이러한 페이지가 더티 페이지이며 나중에 디스크에 플러시된다는 것을 의미합니다.
LRU List 버퍼 풀의 크기가 확실하기 때문에, 즉 프리 링크드 리스트의 프리 캐시 페이지 데이터가 확실하기 때문에 디스크에 있는 데이터 페이지를 프리 캐시 페이지에 계속해서 로딩하게 되면, 무료 무료 캐시 페이지는 연결 목록에서 지속적으로 제거됩니다. 조만간 무료 연결 목록에 무료 캐시 페이지가 없는 순간이 있을 것입니다. 이때 일부 캐시 페이지를 제거해야 합니다. 제거됐나요? 이를 위해서는 캐시 적중률을 사용해야 합니다. 캐시 적중률이 가장 높은 것을 공통적으로 사용하고, 일반적으로 사용되지 않는 것을 제거할 수 있습니다. 따라서 일반적으로 사용되지 않는 캐시 페이지를 확인하기 위해 LRU 연결 목록이 도입되었습니다.
LRU 연결 리스트의 제거 전략은 무엇인가요?
디스크에서 캐시 페이지로 데이터 페이지를 로드할 때 이 캐시 페이지의 설명 데이터 블록을 LRU 연결 목록의 헤드에 넣는다고 가정해 보겠습니다. LRU에 존재하며, 최근에는 로드된 데이터의 캐시 페이지가 LRU 링크드 리스트의 선두에 위치하게 되며, 이후 특정 캐시 페이지가 테일에 추가되면서 쿼리가 발생하게 됩니다. 머리에, 꼬리를 제거해야합니다.
근데 이거 정말 괜찮은 걸까요?
첫 번째 경우 미리 읽기 메커니즘이 파괴됩니다
미리 읽기 메커니즘은 액세스되지 않은 인접한 데이터 페이지를 캐시에 로드하므로 실제로 캐시 페이지 하나만 액세스되고 다른 페이지는 액세스됩니다. 실제로는 로드된 캐시 페이지에 아무도 액세스하지 않았습니다. 아래 그림과 같이 두 캐시 페이지가 모두 LRU 링크 목록 앞에 있습니다. 사용 가능한 캐시 페이지가 없으면 이때 새 페이지를 로드해야 합니다. 데이터 페이지가 있는 경우 LRU 연결 목록 끝에서 소위 "최근에 사용되지 않은 캐시 페이지"를 제거해야 합니까? , 디스크로 플러시한 다음 사용 가능한 캐시 페이지를 확보합니다. 이는 분명히 매우 불합리한 일이다.
두 번째 상황에서는 자주 액세스하는 캐시 페이지가 제거될 수 있습니다.
전체 테이블 스캔을 사용하면 테이블의 모든 데이터 페이지를 디스크에서 버퍼 풀로 한 번에 직접 로드할 수 있습니다. 이때 이 테이블의 모든 데이터 페이지는 각 캐시 페이지에 하나씩 로드될 수 있습니다! 이때 LRU 링크드 리스트 앞부분의 큰 캐시 페이지 목록은 모두 풀 테이블 스캔을 통해 로드된 캐시 페이지일 가능성이 있습니다! 그렇다면 이 전체 테이블 스캔 이후에 이 테이블의 데이터가 향후 거의 사용되지 않는다면 어떻게 될까요? 이때, LRU 링크드 리스트의 tail 부분은 이전에 자주 접속했던 캐시된 페이지들이 모두 있을 수 있습니다! 그런 다음 공간을 확보하기 위해 일부 캐시 페이지를 제거하려는 경우 LRU 목록 끝에서 자주 액세스되는 캐시 페이지를 제거하고 이전 전체 테이블 스캔에서 로드되었던 자주 액세스되지 않는 페이지를 많이 남겨 둡니다. 캐시된 페이지! LRU 알고리즘 최적화: 핫 데이터와 콜드 데이터 분리 아이디어를 기반으로 LRU 연결 리스트 설계MySQL은 LRU 링크 리스트를 설계할 때 실제로 핫 데이터와 콜드 데이터 분리 아이디어를 채택합니다. . LRU 연결 목록은 두 부분으로 분할됩니다. 하나는 핫 데이터이고 다른 하나는 콜드 데이터입니다. 핫 데이터와 콜드 데이터의 비율은 innodb_old_blocks_pct 매개변수에 의해 제어됩니다. 이는 콜드 데이터가 37을 차지함을 의미합니다. %. 데이터 페이지가 처음으로 캐시에 로드되면 캐시 페이지는 실제로 콜드 데이터 영역의 연결된 목록의 선두에 배치됩니다.그런 다음 MySQL은 innodb_old_blocks_time 매개변수를 설계했습니다. 기본값은 1000밀리초입니다. 즉, 데이터 페이지가 캐시 페이지에 로드된 후 1초 후에 캐시 페이지에 액세스하게 됩니다. 핫데이터 영역의 연결리스트 선두로 이동한다. 데이터 페이지를 캐시에 로드한 다음 1초 후에 이 캐시 페이지에 액세스한다고 가정해 보겠습니다. 이는 앞으로 이 캐시 페이지에 자주 액세스할 가능성이 높기 때문에 1초 후에 이 캐시된 페이지에만 액세스한다는 의미입니다. .페이지를 캐시하면 그는 핫 데이터 영역의 링크된 목록의 선두에 캐시 페이지를 배치할 것입니다.
이 경우 사전 읽기 및 전체 테이블 스캔용 데이터는 콜드 데이터 헤더에만 들어가고 처음부터 핫 데이터 영역에 들어가지 않습니다.
LRU 알고리즘은 극도로 최적화되어 있습니다
LRU 링크드 리스트의 핫 데이터 영역에 대한 접근 규칙은 최적화됩니다. 즉, 핫 데이터 영역의 마지막 3/4에 있는 캐시 페이지에 접근하는 경우에만 , 연결리스트의 선두로 이동됩니다. 핫 데이터 영역의 캐시 페이지 중 처음 1/4에 접근하면 링크드 리스트의 선두로 이동하지 않습니다.
예를 들어, 핫 데이터 영역의 연결 리스트에 100개의 캐시 페이지가 있다고 가정하면, 상위 25개의 캐시 페이지는 접속하더라도 연결 리스트의 선두로 이동하지 않습니다. 그러나 다음 75개의 캐시 페이지에 대해서는 액세스되는 한 링크된 목록의 선두로 이동됩니다. 이런 방식으로 그는 연결 리스트에서 노드의 이동을 최대한 줄일 수 있습니다.
캐시 페이지 LRU 연결 목록 제거 타이밍
MySQL은 CRUD를 실행할 때 먼저 다수의 캐시 페이지와 이에 상응하는 여러 연결 목록을 운영합니다. 그런 다음 캐시 페이지가 가득 차면 일부 캐시 페이지를 디스크로 플러시한 다음 이러한 캐시 페이지를 지우고 필요한 데이터 페이지를 캐시 페이지에 로드하는 방법을 찾아야 합니다!
우리는 그가 LRU 연결 리스트를 기반으로 캐시 페이지를 제거한다는 것을 이미 알고 있는데, 언제 LRU 연결 리스트의 콜드 데이터 영역에 있는 캐시 페이지를 디스크로 플러시했습니까? 실제로 그에게는 다음과 같은 세 가지 기회가 있습니다.
LRU 끝의 일부 캐시 페이지를 디스크로 정기적으로 플러시합니다.
백그라운드 스레드는 예약된 작업을 실행하여 콜드 데이터 영역을 플러시합니다. 가끔 LRU 연결 목록을 페이지 끝에 있는 일부 캐시 페이지를 디스크에 플러시하고, 이러한 캐시 페이지를 지우고, 무료 연결 목록에 다시 추가합니다.
플러시 링크드 리스트의 일부 캐시 페이지를 주기적으로 디스크로 플러시
LRU 링크드 리스트의 콜드 데이터 영역에 있는 캐시 페이지만 디스크로 플러시하면 충분하지 않습니다. 링크드 리스트의 핫 데이터 영역에는 캐시 페이지가 많기 때문에 자주 수정될 수도 있습니다. 디스크에 플러시되지는 않을까요?
따라서 이 백그라운드 스레드는 MySQL이 사용량이 많지 않을 때 플러시 연결 목록의 모든 캐시 페이지도 디스크로 플러시합니다. 이러한 방식으로 사용자가 수정한 데이터는 조만간 디스크로 플러시됩니다!
플러시 연결 목록의 캐시 페이지 웨이브가 디스크에 플러시되는 한 이러한 캐시 페이지도 플러시 연결 목록과 lru 연결 목록에서 제거된 다음 자유 연결 목록에 추가됩니다!
그래서 전체적인 효과는 지속적으로 캐시 페이지에 데이터를 로드하고 캐시 데이터를 지속적으로 쿼리하고 수정하면 무료 연결 목록의 캐시 페이지가 계속 감소하고 플러시 연결 목록의 캐시 페이지가 계속 증가하는 것입니다. . , lru 연결 목록의 캐시 페이지는 지속적으로 증가하고 이동합니다.
반면에 백그라운드 스레드는 lru 연결 목록의 콜드 데이터 영역의 캐시 페이지와 플러시 연결 목록의 캐시 페이지를 디스크로 지속적으로 플러시하여 캐시 페이지를 지운 다음 캐시를 비웁니다. 플러시 링크드 리스트와 lru 링크드 리스트의 페이지가 감소하고, 프리 리스트의 캐시 페이지가 증가합니다.
프리 링크드 리스트에는 프리 캐시 페이지가 없습니다
프리 링크 리스트를 모두 사용한 경우, 이때 디스크의 데이터 페이지를 프리 캐시 페이지로 로드하려면 다음 위치에서 로드됩니다. LRU 링크드 리스트의 콜드 데이터 영역. 마지막에 캐시 페이지가 발견되면 가장 적게 사용되는 캐시 페이지여야 합니다! 그런 다음 디스크에 플러시하고 지운 다음 데이터 페이지를 무료 캐시 페이지에 로드하세요!
세 가지 연결 목록의 사용법을 요약하면 버퍼 풀을 사용할 때 실제로는 디스크의 데이터 페이지를 캐시 페이지로 자주 로드한 다음 무료 연결 목록, 플러시 연결 목록 및 lru 연결 목록을 로드합니다. 예를 들어 캐시 페이지에 데이터가 로드되면 해당 캐시 페이지는 자유 연결 리스트에서 제거되고 lru 연결 리스트의 콜드 데이터 영역 헤드가 제거됩니다. 캐시 페이지에 배치됩니다.
그러다가 캐시 페이지를 수정하면 더티 페이지가 플러시 연결 리스트에 기록되고, lru 연결 리스트도 콜드 데이터 영역에서 핫 데이터 영역의 선두로 이동할 수 있습니다.
캐시 페이지를 쿼리하면 캐시 페이지가 lru 연결 리스트의 핫 데이터 영역으로 이동되거나, 핫 데이터 영역의 헤드로 이동될 수도 있습니다.
리두 로그 버퍼 리두 로그 버퍼
InnoDB에는 버퍼 풀(줄여서 bp)이 있습니다. bp는 데이터베이스 페이지의 캐시입니다. InnoDB에 대한 모든 수정 작업은 먼저 bp 페이지에서 수행됩니다. 그런 다음 해당 페이지는 더티(더티 페이지)로 표시되고 이후에 마스터 스레드 또는 A에 배치됩니다. 전용 클리닝 스레드는 주기적으로 이러한 페이지를 디스크(디스크 또는 SSD)에 씁니다.
이 방법의 장점은 각 쓰기 작업마다 디스크를 작동하지 않아 대량의 무작위 IO가 발생하지 않는다는 것입니다. 정기적인 브러싱은 페이지에 대한 여러 수정 사항을 하나의 IO 작업으로 병합할 수 있으며 동시에 비동기 쓰기도 감소합니다. 액세스 지연. 그러나 더티 페이지가 디스크에 플러시되기 전에 서버가 비정상적으로 종료되면 이러한 수정 작업이 손실됩니다. 쓰기 작업이 진행 중이면 데이터 파일이 손상되어 데이터베이스를 사용할 수 없게 될 수도 있습니다.
위의 문제를 방지하기 위해 Innodb는 페이지에 대한 모든 수정 작업을 특수 파일에 기록하고 데이터베이스가 시작될 때 이 파일에서 복구 작업을 수행합니다. 이 파일은 리두 로그 파일입니다. 이 기술은 bp 페이지 새로 고침을 지연시켜 데이터베이스 처리량을 향상시키고 액세스 대기 시간을 효과적으로 줄입니다.
문제는 리두 로그 작업(물론 빠른 순차 IO)을 작성하는 데 드는 추가 오버헤드와 데이터베이스가 시작될 때 작업을 재개하는 데 필요한 시간입니다.
리두 로그는 리두 로그 버퍼와 리두 로그 파일(디스크 파일 섹션에 소개됨)의 두 부분으로 구성됩니다. InnoDB는 트랜잭션을 지원하는 스토리지 엔진으로, 트랜잭션의 모든 로그는 먼저 리두 로그 파일에 기록되어야 하며, 트랜잭션의 커밋 작업이 완료될 때까지 전체 트랜잭션 작업은 완료되지 않습니다. 리두 로그 버퍼가 리두 로그 파일에 기록될 때마다 fsync 작업을 호출해야 합니다. 왜냐하면 리두 로그 버퍼는 운영 체제의 버퍼 시스템에 먼저 내용을 기록할 뿐 직접 기록되는지 보장하지 않기 때문입니다. 디스크에 fsync 작업을 수행해야 합니다. 따라서 디스크 성능은 트랜잭션 제출 성능을 어느 정도 결정합니다(리두 로그 디스크 삭제 메커니즘은 나중에 소개됩니다).
InnoDB 스토리지 엔진은 먼저 리두 로그 정보를 리두 로그 버퍼에 넣은 다음 특정 빈도로 리두 로그 파일로 플러시합니다. 리두 로그 버퍼는 일반적으로 매우 크게 설정할 필요가 없습니다. 리두 로그 버퍼는 일반적으로 매초마다 로그 파일로 플러시되며, 기본값은 8MB인 Innodb_log_buffer_size 구성 매개변수로 제어할 수 있습니다.
Double Write
Insert Buffer가 InnoDB 스토리지 엔진의 성능 향상을 가져온다면 Double wtite는 InnoDB 스토리지 엔진에 데이터 페이지의 안정성을 제공합니다.
InnoDB의 페이지 크기는 일반적으로 16KB이며, 데이터 검증도 이 16KB를 기준으로 계산됩니다. 디스크에 데이터를 쓰는 작업은 페이지 단위로 수행됩니다. 우리는 파일 시스템이 대부분의 경우 대규모 데이터 페이지(예: InnoDB의 16KB)에서 원자적이지 않기 때문에 서버가 다운되면 쓰기의 일부만 수행될 수 있음을 의미합니다. 16K의 데이터를 4K에 쓸 때 시스템 정전이 발생하고 OS 충돌이 발생했습니다. 이 경우에는 부분 페이지 쓰기 문제였습니다.
숙련된 DBA라면 쓰기 실패가 발생하면 MySQL이 Redo 로그를 기반으로 복구할 수 있다고 생각할 수도 있습니다. 이런 방법이지만 리두 로그에 기록되는 것은 오프셋 800, 'aaaa' 레코드 쓰기 등 페이지의 물리적인 수정임을 분명히 이해해야 한다. 페이지 자체가 손상된 경우 다시 실행할 필요가 없습니다. MySQL은 복구 프로세스 중에 페이지의 체크섬을 확인합니다. 체크섬은 페이지의 마지막 트랜잭션 번호입니다. 부분 페이지 쓰기 문제가 발생하면 페이지가 손상되어 페이지의 트랜잭션 번호를 찾을 수 없습니다. InnoDB의 관점에서 이러한 데이터 페이지는 체크섬 확인을 통과할 수 없으며 복구할 수 없습니다. 강제로 검증을 통과하더라도 InnoDB의 현재 로그 유형 중 일부(일부는 논리 연산임)는 멱등성을 가질 수 없기 때문에 충돌로부터 복구할 수 없습니다.
이 문제를 해결하기 위해 InnoDB는 이중 쓰기 버퍼를 구현합니다. 간단히 말해서 데이터 페이지를 쓰기 전에 먼저 독립적인 물리적 파일 위치(ibdata)에 데이터 페이지를 쓴 다음 데이터 페이지에 씁니다. 이처럼 머신이 다운됐다가 다시 시작했을 때 데이터 페이지가 손상되면 리두 로그를 적용하기 전에 해당 페이지의 복사본을 통해 페이지를 복원한 뒤 리두 로그를 다시 작성하는 것이 이중 쓰기이다. . innodb 스토리지 엔진에 이중 쓰기 기술이 가져오는 것은 데이터 페이지의 신뢰성입니다. 이중 쓰기 기술은 아래에서 분석됩니다
如上图所示,Double Write 由两部分组成,一部分是内存中的 double write buffer,大小为2MB,另一部分是物理磁盘上共享表空间连续的128个页,大小也为2MB。在对缓冲池的脏页进行刷新时,并不直接写磁盘,而是通过 memcpy 函数将脏页先复制到内存中的该区域,之后通过 double write buffer 再分两次,每次1MB顺序地写入共享表空间的物理磁盘上,然后马上调用 fsync 函数,同步磁盘,避免操作系统缓冲写带来的问题。在完成double write 页的写入后,再将 double wirite buffer 中的页写入各个表空间文件中。
在这个过程中,doublewrite 是顺序写,开销并不大,在完成 doublewrite 写入后,在将 double write buffer写入各表空间文件,这时是离散写入。
如果操作系统在将页写入磁盘的过程中发生了崩溃,在恢复过程中,InnoDB 存储引擎可以从共享表空间中的double write 中找到该页的一个副本,将其复制到表空间文件中,再应用重做日志。
InnoDB 存储引擎第二部分:后台线程
IO 线程
在 InnoDB 中使用了大量的 AIO(Async IO) 来做读写处理,这样可以极大提高数据库的性能。在 InnoDB 1.0 版本之前共有4个 IO Thread,分别是 write,read,insert buffer和log thread,后来版本将 read thread和 write thread 分别增大到了4个,一共有10个了。
- read thread : 负责读取操作,将数据从磁盘加载到缓存page页。4个
- write thread:负责写操作,将缓存脏页刷新到磁盘。4个
- log thread:负责将日志缓冲区内容刷新到磁盘。1个
- insert buffer thread :负责将写缓冲内容刷新到磁盘。1个
Purge 线程
事务提交之后,其使用的 undo 日志将不再需要,因此需要 Purge Thread 回收已经分配的 undo 页。show variables like '%innodb*purge*threads%';
Page Cleaner 线程
作用是将脏数据刷新到磁盘,脏数据刷盘后相应的 redo log 也就可以覆盖,即可以同步数据,又能达到 redo log 循环使用的目的。会调用write thread线程处理。show variables like '%innodb*page*cleaners%';
InnoDB 存储引擎第三部分:磁盘文件
InnoDB 的主要的磁盘文件主要分为三大块:一是系统表空间,二是用户表空间,三是 redo 日志文件和归档文件。
二进制文件(binlong)等文件是 MySQL Server 层维护的文件,所以未列入 InnoDB 的磁盘文件中。
系统表空间和用户表空间
系统表空间包含 InnoDB 数据字典(元数据以及相关对象)并且 double write buffer , change buffer , undo logs 的存储区域。
系统表空间也默认包含任何用户在系统表空间创建的表数据和索引数据。
系统表空间是一个共享的表空间,因为它是被多个表共享的。
系统表空间是由一个或者多个数据文件组成。默认情况下,1个初始大小为10MB,名为 ibdata1 的系统数据文件在MySQL的data目录下被创建。用户可以使用 innodb_data_file_path 对数据文件的大小和数量进行配置。
innodb_data_file_path 的格式如下:
innodb_data_file_path=datafile1[,datafile2]...
用户可以通过多个文件组成一个表空间,同时制定文件的属性:
innodb_data_file_path = /db/ibdata1:1000M;/dr2/db/ibdata2:1000M:autoextend
这里将 /db/ibdata1 和 /dr2/db/ibdata2 两个文件组成系统表空间。如果这两个文件位于不同的磁盘上,磁盘的负载可能被平均,因此可以提高数据库的整体性能。两个文件的文件名之后都跟了属性,表示文件 ibdata1 的大小为1000MB,文件 ibdata2 的大小为1000MB,而且用完空间之后可以自动增长。
设置 innodb_data_file_path 参数之后,所有基于 InnoDB 存储引擎的表的数据都会记录到该系统表空间中,如果设置了参数 innodb_file_per_table ,则用户可以将每个基于 InnoDB 存储引擎的表产生一个独立的用户空间。
用户表空间的命名规则为:表名.ibd。通过这种方式,用户不用将所有数据都存放于默认的系统表空间中,但是用户表空间只存储该表的数据、索引和插入缓冲BITMAP等信息,其余信息还是存放在默认的系统表空间中。
下图显示 InnoDB 存储引擎对于文件的存储方式,其中frm文件是表结构定义文件,记录每个表的表结构定义。
Redo 로그 파일 및 아카이브 파일
기본적으로 InnoDB 스토리지 엔진의 데이터 디렉터리에는 ib_logfile0과 ib_logfile1이라는 두 개의 파일이 있으며, 이는 InnoDB의 트랜잭션 로그를 기록하는 파일입니다. InnoDB 스토리지 엔진.
InnoDB의 데이터 저장 파일에 오류가 발생하면 redo 로그 파일을 활용하면 유용할 수 있습니다. InnoDB 스토리지 엔진은 리두 로그 파일을 사용하여 데이터를 올바른 상태로 복원하여 데이터 정확성과 무결성을 보장할 수 있습니다.
각 InnoDB 스토리지 엔진에는 최소 1개의 리두 로그 파일이 있고, 각 파일 그룹에는 최소 2개의 리두 로그 파일과 기본 ib_logfile0 및 ib_logfile1이 있습니다.
더 높은 안정성을 얻기 위해 사용자는 여러 미러 로그 그룹을 설정하고 서로 다른 파일 그룹을 서로 다른 디스크에 배치하여 리두 로그의 고가용성을 향상시킬 수 있습니다.
로그 그룹의 각 리두 로그 파일은 동일한 크기를 가지며 [루프 쓰기] 방식으로 작동합니다. InnoDB 스토리지 엔진은 먼저 리두 로그 파일 1을 작성합니다. 파일이 가득 차면 리두 로그 파일 2로 전환됩니다. 리두 로그 파일 2도 가득 차면 리두 로그 1로 전환됩니다.
사용자는 Innodb_log_file_size를 사용하여 리두 로그 파일의 크기를 설정할 수 있으며 이는 InnoDB 스토리지 엔진의 성능에 큰 영향을 미칩니다.
리두 로그 파일을 너무 크게 설정하면 데이터 손실 시 복구하는 데 오랜 시간이 걸릴 수 있고, 너무 작게 설정하면 리두 로그 파일이 너무 작아서 체크포인트가 발생할 수 있습니다. 더티 페이지를 자주 새로 고쳐야 하는 기반 검사로 인해 성능이 저하됩니다.
Redo 로그 플러싱 메커니즘
InnoDB는 데이터 파일과 로그 파일을 플러시하기 위한 WAL(리두 로그 미리 쓰기)과 Force-log-at-commit의 두 가지 규칙을 따르며 둘 다 트랜잭션의 내구성을 보장합니다. WAL에서는 데이터 변경 사항을 디스크에 기록하기 전에 먼저 메모리에 있는 로그를 디스크에 기록해야 합니다. Force-log-at-commit에서는 트랜잭션이 커밋될 때 생성된 모든 로그를 디스크에 플러시해야 합니다. 새로 고침이 성공하면 버퍼 풀의 데이터가 디스크에 새로 고쳐지기 전에 데이터베이스가 충돌하는 경우 데이터베이스는 다시 시작할 때 로그에서 데이터를 복구할 수 있습니다.
위 그림에서 볼 수 있듯이 InnoDB는 버퍼 풀의 데이터를 변경할 때 먼저 관련 변경 사항을 리두 로그 버퍼에 기록한 다음 제 시간에(예: 초당 1회) 디스크에 씁니다. 새로 고침 메커니즘) 또는 트랜잭션이 커밋되면 Redo 로그가 디스크에 기록된 후에만 Force-log-at-commit 원칙을 준수하며 버퍼 풀의 변경된 데이터는 다음에 따라 디스크에 기록됩니다. WAL 원칙을 준수하는 체크포인트 메커니즘입니다.
체크포인트 타이밍 메커니즘에는 리두 로그 파일이 꽉 찼다는 판단이 있습니다. 따라서 위에서 언급한 것처럼 리두 로그 파일이 너무 작아서 자주 채워지면 체크포인트에서 변경된 데이터를 쓰는 경우가 많습니다. 디스크에 저장되어 성능 지터가 발생합니다.
운영 체제의 파일 시스템에는 캐시가 있습니다. InnoDB가 디스크에 데이터를 쓸 때 파일 시스템의 캐시에만 쓸 수 있으며 실제 "안전성"은 없습니다.
InnoDB의 innodb_flush_log_at_trx_commit 속성은 트랜잭션이 커밋될 때마다 InnoDB의 동작을 제어할 수 있습니다. 속성 값이 0이면 트랜잭션이 커밋되면 리두 로그가 기록되지 않지만 메인 스레드가 제 시간에 쓸 때까지 기다립니다. 속성 값이 1이면 트랜잭션이 커밋되면 리두 로그가 기록됩니다. 파일에 기록됩니다. 시스템은 파일 시스템의 fsync를 캐시하고 호출하여 실제로 파일 시스템 버퍼의 데이터를 디스크 저장소에 기록하여 속성 값이 2일 때 데이터 손실이 발생하지 않도록 합니다. 로그 파일도 기록됩니다. 트랜잭션이 커밋될 때 파일 시스템에 캐시되지만 fsync를 호출하지는 않지만 파일 시스템이 캐시를 디스크에 쓸 시기를 결정할 수 있습니다.
로그의 플러시 메커니즘은 아래 그림과 같습니다.
Innodb_flush_log_at_commit은 InnoDB의 쓰기 효율성 및 데이터 보안과 관련된 InnoDB 성능 튜닝을 위한 기본 매개변수입니다. 매개변수 값이 0이면 쓰기 효율성이 가장 높지만 데이터 보안은 가장 낮습니다. 매개변수 값이 1이면 쓰기 효율성이 가장 낮지만 매개변수 값이 2이면 데이터 보안이 가장 높습니다. , 둘 다 중간 수준이며 일반적으로 속성을 설정하는 것이 좋습니다. 보안을 강화하려면 값을 1로 설정해야 하며, 1로 설정해야 트랜잭션의 내구성을 보장할 수 있습니다.
UPDATE 문을 사용하여 InnoDB 스토리지 엔진에 대해 자세히 알아보세요
위의 InnoDB 스토리지 엔진 기본 아키텍처 소개와 함께 UPDATE 데이터 업데이트의 구체적인 프로세스를 분석해 보겠습니다.
이 그림을 위쪽과 아래쪽으로 나누어 보겠습니다. 위쪽은 MySQL Server 계층 처리 흐름이고, 아래쪽은 MySQL InnoDB 스토리지 엔진 처리 흐름입니다.
MySQL 서버 계층 처리 흐름
이 처리 흐름 부분은 어떤 스토리지 엔진과 관련이 없습니다. 구체적인 단계는 다음과 같습니다.
다양한 사용자 작업이 데이터베이스 연결 풀을 통해 백그라운드 SQL 실행을 트리거합니다. 웹 프로젝트와 함께 제공되는 dbcp, c3p0, druid 등은 데이터베이스 서버의 데이터베이스 연결 풀과 네트워크 연결을 설정합니다.
데이터베이스 연결 풀의 스레드가 요청을 수신한 후 수신된 sql 문을 SQL 인터페이스를 통해 쿼리 파서에 응답하고, 쿼리를 파싱합니다. 서버는 sql 구문에 따라 sql을 파싱하여 쿼리할 테이블의 어떤 필드와 쿼리 조건이 무엇인지 결정합니다. 그런 다음 쿼리 최적화 프로그램에 의해 처리되어
에 대한 최적의 실행 계획 세트를 선택한 다음 실행기는 스토리지 엔진의 일련의 인터페이스를 호출하고 계획을 실행하며 전체 SQL 실행을 완료합니다. 이 프로세스 부분은 위에서 분석한 Select 요청 처리 프로세스의 분석과 기본적으로 일치합니다.
위 그림과 같이 특정 실행 문은 스토리지 엔진에 의해 완료되어야 합니다.
사용자 테이블에서 id=10으로 데이터를 업데이트합니다. 버퍼링된 경우 풀에 해당 데이터 조각이 없으면 업데이트된 데이터의 원본 데이터를 먼저 디스크에서 버퍼 풀로 로드해야 합니다.
동시에 동시에 업데이트되는 데이터의 보안을 보장하기 위해 이 데이터는 다른 거래가 업데이트되지 않도록 먼저 잠깁니다.
그런 다음 업데이트하기 전에 값을 백업하고 실행 취소 로그에 기록합니다(트랜잭션이 롤백될 때 이전 데이터를 쉽게 검색할 수 있도록). 예를 들어 업데이트 문은 업데이트된 필드 이전의 값을 저장합니다.
버퍼 풀에 있는 캐시 데이터를 최신 데이터로 업데이트하면 이때 메모리에 있는 데이터는 더티 데이터입니다(메모리에 있는 데이터와 디스크에 있는 데이터가 일치하지 않습니다)
지금까지 메모리 데이터를 수정한 후 Redo Log Buffer 로그 버퍼를 기록했다는 것을 위에서 알고 있습니다. 이때 MySQL이 충돌하면 메모리 데이터와 Redo Log Buffer 데이터가 손실되지만, 업데이트로 인해 이때 데이터가 손실됩니다. 문이 트랜잭션을 제출하지 않은 경우 이는 성공적으로 실행되지 않았음을 의미합니다. 이때 MySQL이 다운되고 메모리의 데이터가 손실되었음을 알 수 있습니다. 디스크의 데이터는 그대로 유지됩니다.
다음 단계는 트랜잭션을 커밋하는 것입니다. 이때 리두 로그는 특정 전략에 따라 리두 로그 버퍼에서 디스크 파일로 플러시됩니다. 이때 이 전략은 innodb_flush_log_at_trx_commit을 통해 구성됩니다.
innodb_flush_log_at_trx_commit=0, 이는 트랜잭션을 제출해도 리두 로그 버퍼의 데이터가 디스크 파일로 플러시되지 않음을 의미합니다. 이때 트랜잭션을 제출했을 수 있으며 결과적으로 mysql이 다운되고 모든 것이 중단됩니다. 메모리의 데이터가 손실되므로 이 접근 방식은 권장되지 않습니다.
innodb_flush_log_at_trx_commit=1, 리두 로그는 메모리에서 디스크 파일로 플러시됩니다. 트랜잭션이 성공적으로 제출되는 한 리두 로그는 디스크에 있어야 하므로 이때 MySQL이 충돌하면 데이터를 복원할 수 있습니다. Redo Log 로그를 기반으로 합니다.
innodb_flush_log_at_trx_commit=2, 트랜잭션 커밋 시 디스크 파일을 직접 입력하는 대신 디스크 파일에 해당하는 os 캐시에 redo 로그를 기록합니다. os 캐시에 있는 데이터가 디스크 파일에 기록되기까지 1초 정도 걸릴 수 있습니다. . 안으로 들어가세요.
트랜잭션이 제출되면 binlog도 동시에 기록됩니다. binlog에는 다양한 디스크 플러시 전략이 있습니다. 이때 기본값은 0입니다. binlog를 디스크에 기록하면 실제로 디스크 파일에 직접 들어가는 것이 아니라 os 캐시 메모리 캐시에 들어갑니다. 일반적으로 데이터가 손실되지 않도록 하기 위해 이중 1 전략을 구성하고 Redo Log 및 Binlog 디스크 배치 전략 모두에 대해 1을 선택합니다.
Binlog가 배치된 후 Binlog 파일 이름, 파일 경로 정보 및 커밋 표시가 Redo 로그에 동기화 순서로 기록됩니다. 이 단계의 중요성은 redo 로그를 binlog 로그와 일관되게 유지하는 것입니다. 커밋 표시는 트랜잭션이 성공적으로 제출되었는지 여부를 결정하는 중요한 기준입니다. 예를 들어 5단계 또는 6단계가 성공적으로 실행된 후 MySQL이 충돌하는 경우 이번에는 Redo 로그에 최종 트랜잭션 커밋 표시가 없기 때문에 이 트랜잭션은 실패했다고 판단할 수 있습니다. redo 로그 파일에 이번 업데이트에 대한 로그가 있다고는 할 수 없지만, binlog 로그 파일에는 이번 업데이트에 대한 로그가 없기 때문에 데이터 불일치 문제는 없을 것이다.
위 작업을 완료한 후 메모리 데이터가 수정되고 트랜잭션이 제출되었으며 로그가 디스크에 배치되었지만 디스크 데이터가 동시에 수정되지 않았습니다. InnoDB 스토리지 엔진의 백그라운드에는 IO 스레드가 있습니다. 데이터베이스 압력이 가장 낮은 기간 동안 트랜잭션에 의해 업데이트되었지만 아직 디스크에 기록될 시간이 없는 버퍼 풀의 데이터(더티 데이터) , 디스크 데이터와 메모리 데이터를 더 이상 사용할 수 없기 때문에) 일관성) 트랜잭션의 지속성을 완료하기 위해 디스크에 플러시됩니다.
그래서 InnoDB의 작성 과정은 아래 그림으로 표현할 수 있습니다
추천 학습: mysql 비디오 튜토리얼
위 내용은 흥미로운 mysql 아키텍처와 InnoDB 스토리지 엔진 지식에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!