MySQL Storage Engine InnoDB, см. Структуру данных InnoDB из нижнего слоя

база данных

InnoDB — это механизм хранения, который поддерживает безопасность транзакций, а также является механизмом хранения по умолчанию для mysql. В этой статье в основном представлен формат записи строки InnoDB и принцип реализации страниц данных с точки зрения структуры данных, а также рассматривается механизм хранения InnoDB снизу.

Основное содержание этой статьи основано на буклете Nuggets «Понимание MySQL с самого начала». Если вы хотите узнать больше, рекомендуется купить буклет Nuggets для чтения.

Введение в InnoDB

Всем известно, что данные в mysql хранятся на физических дисках, а реальная обработка данных выполняется в памяти. Поскольку скорость чтения и записи диска очень низкая, если диск часто читается и записывается для каждой операции, производительность должна быть очень низкой. Для вышеуказанной проблемыInnoDB делит данные на несколько страниц и использует страницу как основную единицу взаимодействия между диском и памятью.Размер общей страницы составляет 16 КБ.. В этом случае по крайней мере 1 страница данных читается в памяти за один раз или 1 страница данных записана на диск. Улучшает производительность за счет уменьшения количества взаимодействий на диске памяти.

На самом деле, это типичная идея дизайна кэша, а общий кэш в основном из时间维度или空间维度Обдуманный:

  1. 时间维度: если часть данных используется, существует высокая вероятность того, что она будет использоваться снова в следующий период времени. Это можно считать热点数据缓存Все относятся к реализации этой идеи.
  2. 空间维度: если часть данных используется, данные, хранящиеся рядом с ней, скорее всего, скоро будут использованы.InnoDB的数据页и操作系统的页缓存Это воплощение этой идеи.

Формат строки InnoDB

MySQL вставляет данные в таблицу данных в единицах записей (одна строка данных), а способ хранения этих записей на диске называется行格式. MySQL поддерживает 4 различных типа форматов строк:Compact,Redundant(Раньше эта статья не будет вводить его подробно),Dynamic,Compressed. 我们可以在创建或修改表的语句中指定行格式:

CREATE TABLE 表名 (列的信息) ROW_FORMAT=行格式名称
ALTER TABLE 表名 ROW_FORMAT=行格式名称

Например, мы хотим создать формат строкиCompact, набор символовasciiтехническая спецификацияrecord_format_demo, sql выглядит следующим образом:

mysql> CREATE TABLE record_format_demo (
    ->     c1 VARCHAR(10),
    ->     c2 VARCHAR(10) NOT NULL,
    ->     c3 CHAR(10),
    ->     c4 VARCHAR(10)
    -> ) CHARSET=ascii ROW_FORMAT=COMPACT;
Query OK, 0 rows affected (0.03 sec)

Предположим, мыrecord_format_demoВ таблицу вставлены 2 строки данных:

mysql> SELECT * FROM record_format_demo;
+------+-----+------+------+
| c1   | c2  | c3   | c4   |
+------+-----+------+------+
| aaaa | bbb | cc   | d    |
| eeee | fff | NULL | NULL |
+------+-----+------+------+
2 rows in set (0.00 sec)

КОМПАКТНЫЙ формат строки

COMPACT行格式

Как видно из рисунка выше, полная запись содержит记录的额外信息и记录的真实数据две части.

Записана дополнительная информация

Регистрируемая дополнительная информация в основном включает 3 категории:变长字段列表,NULL值列表и记录头信息.

список полей переменной длины

MySQL поддерживает некоторые типы данных переменной длины (например,VARCHAR(M),TEXTд.), объем памяти, занимаемый ими для хранения данных, не является фиксированным, а будет меняться при изменении содержимого хранилища. Чтобы точно описать этот тип данных, объем памяти, занимаемый этим полем переменной длины, должен включать:

  1. реальное содержание данных
  2. Занятые байты

В линейном формате CompactДлина байтов, занимаемых реальными данными всех полей переменной длины, сохраняется в начале записи, таким образом формируется список длин полей переменной длины.Количество байтов, занимаемых данными каждого поля переменной длины, равно в порядке столбцов.逆序хранить.

мы начинаем сrecord_format_demoВозьмите первую строку данных в качестве примера. так какc1,c2иc4все преобразуются в типы данных (VARCHAR(10)), поэтому сохраните длину этих трех столбцов в начале записи.变长字段列表

Еще одна вещь, которую следует отметить, это то, чтоВ списке длин полей переменной длины сохраняется только длина, занятая содержимым столбца, значение которого не равно NULL, а длина столбца, значение которого равно NULL, не сохраняется.. То есть для второй записи, поскольку значение столбца c4 равно NULL, список длин полей переменной длины второй записи должен хранить только длины столбцов c1 и c2.变长字段列表

Список значений NULL

Для столбцов, которые могут быть нулевыми, для экономии места для хранения, MySQL не будетNULLзначение хранится в记录的真实数据часть. вместо этого он будет храниться в记录的额外信息внутриNULL值列表середина.

Конкретный метод заключается в том, чтобы сначала разрешить хранение в таблице статистики.NULLстолбец значений, затем сохраните каждый разрешенныйNULLСтолбец значений соответствует двоичному биту (1: значение равноNULL0: значение неNULL) используется для указания, следует ли хранитьNULLзначения в обратном порядке. Правила MySQLСписок значений NULL должен быть представлен целым числом байт битов, если количество используемых двоичных битов не является целым числом байтов, добавьте 0 к старшему биту байта. соответствоватьrecord_format_demoВ таблице,c1,c3,c4Всем разрешено хранить значения NULL. Первые две записи заполненыNULLСхема после списка значений выглядит так:

NULL值列表

запись информации в заголовке

Информация заголовка записи состоит из фиксированных 5 байтов (40 бит), и разные биты представляют разные значения:记录头信息Пока не подробно.

зарегистрированы реальные данные

В дополнение к конкретным данным каждого столбца записанные реальные данные также автоматически добавят некоторые скрытые данные столбца.

имя столбца Это необходимо занимать место описывать
row_id нет 6 байт Идентификатор строки, который однозначно идентифицирует запись
transaction_id да 6 байт номер транзакции
roll_pointer да 7 байт указатель отката

На самом деле настоящие имена этих столбцов на самом деле: DB_ROW_ID, DB_TRX_ID, DB_ROLL_PTR, которые для красоты пишутся как row_id, transaction_id и roll_pointer.

только если база данных не определена主键или唯一键, скрыть столбецrow_idбудет существовать и использовать его как таблицу данных主键. потому что столrecord_format_demoПервичный ключ не определен, поэтому сервер MySQL добавит указанные выше три столбца к каждой записи. Теперь посмотри на это плюс记录的真实数据Структура данных двух записей:记录的真实数据

Формат хранения столбца CHAR(M)

Для столбца типа CHAR(M), когда столбец использует набор символов фиксированной длины, количество байтов, занимаемых столбцом, не будет добавлено в список длин полей переменной длины, ноЕсли используется набор символов переменной длины, количество байтов, занимаемых столбцом, также будет добавлено в список длин полей переменной длины.. Следует также отметить, что набор символов переменной длиныCHAR(M)Столбцы типа должны занимать не менееMбайт, покаVARCHAR(M)Но такого требования нет. Например, для использованияutf8Набор символовCHAR(10)Для столбца диапазон длины байта данных, хранящихся в этом столбце,10~30байт, даже если мы храним пустую строку в столбце10байт.

данные переполнения строки

Максимальные данные, которые может хранить VARCHAR(M)

MySQL имеет ограничение на максимальный объем памяти, занимаемый записью, за исключениемBLOBилиTEXTвне столбца типа,Общая длина байтов, занимаемых всеми остальными столбцами (за исключением скрытых столбцов и информации заголовка записи), не может превышать 65 535 байт.. Можно предположить, что,Объем памяти, занимаемый строкой записей MySQL, не может превышать 65535 байт.. Помимо данных самого столбца, эти 65535 байт также включают в себя некоторые другие данные (накладные расходы на хранение), например, чтобы хранить столбец типа VARCHAR(M), нам фактически нужно занимать 3 части дискового пространства:

  1. реальные данные
  2. Реальные данные занимают байты в длину
  3. Идентификация значения NULL, если столбец имеет атрибут NOT NULL, в этой части памяти нет необходимости

Предположениеvarchar_size_demoтолько одинVARCHARtype поле, то максимально занимаемое полем 65532 байта. Поскольку длина реальных данных может занимать 2 байта,NULL值标识Требуется 1 байт. еслиVARCHARТип столбца неNOT NULLсвойства, которые могут хранить не более65532байт данных. если столбецasciiНабор символов, соответствующее максимальное количество символов до65532;еслиutf8набор символов, соответствующее максимальное количество символов равно21844.

Переполнение, вызванное слишком большим количеством данных в записи

Мы используем набор символов ascii подvarchar_size_demoВ качестве примера вставьте запись:

mysql> CREATE TABLE varchar_size_demo(
    ->       c VARCHAR(65532)
    -> ) CHARSET=ascii ROW_FORMAT=Compact;
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO varchar_size_demo(c) VALUES(REPEAT('a', 65532));
Query OK, 1 row affected (0.00 sec)

Базовой единицей взаимодействия между диском и памятью в mysql является страница, обычно 16 КБ, 16384 байта, и строка записей может занимать максимум65535байт, что приводит кКогда одна страница не может хранить следующую строку данных.在Compact和Redundant行格式中,对于占用存储空间非常大的列,在记录的真实数据处只会存储该列的一部分数据,把剩余的数据分散存储在几个其他的页中,然后记录的真实数据处用20个字节存储指向这些页的地址,从而可以找到剩余数据所在的页,如图所示:行溢出数据этоТолько первые 768 байт данных в столбце и адрес, указывающий на другие страницы, хранятся в реальных данных этой записи, а затем остальные данные хранятся в других страницах.行溢出, те страницы, которые хранят более 768 байт, также называются溢出页.行溢出数据

критическая точка переполнения линии

MySQL требует, чтобы на странице хранились как минимум две строки записей.. Вышеvarchar_size_demoТаблица, например, имеет только один столбецc, мы вставляем две записи в эту таблицу, сколько байт данных вставляется в каждую запись хотя бы до того, как произойдет переполнение строки? Это должно проанализировать, как используется пространство на странице.

  1. Помимо хранения наших записей, каждая страница также должна хранить некоторую дополнительную информацию, около 132 байт.
  2. Дополнительная информация, необходимая для каждой записи, составляет 27 байт.

Предполагая, что количество байтов данных, хранящихся в столбце, равно n, если вы хотите убедиться, что столбец не переполняется, вам необходимо выполнить:

132 + 2×(27 + n) < 16384

оказатьсяn < 8099.Это означает, что если данные, хранящиеся в столбце, меньше 8099 байт, то столбец не становится столбцом переполнения.. Это значение меньше, если в таблице несколько столбцов.

Динамические и сжатые форматы строк

Формат строки по умолчанию в mysql:Dynamic.DynamicиCompressedформат строки иCompactФормат очень похож, только в обработке行溢出Есть расхождения в данных.DynamicиCompressedформат строки не будет в记录的真实数据Вместо хранения первых 768 байтов сохраните все байты на других страницах.CompressedФормат строки использует алгоритм сжатия для сжатия страницы для экономии места.Dynamic

Структура страницы данных InnoDB

Мы уже знаем, что страница является основной единицей пространства хранения управления InnoDB, а размер страницы обычно составляет 16 КБ. В InnoDB есть много разных типов страниц, предназначенных для разных целей.存储数据记录Пейдж, официально известный как索引页. Поскольку индекс еще не введен, назовем его пока数据页Бар.

Краткий обзор структуры страницы данных

Страница данных может быть разделена на несколько частей по структуре, и разные части имеют разные функции, как показано на следующем рисунке:数据页结构

Страница данных InnoDB разделена на 7 частей, и содержание этих 7 частей примерно описано ниже.

название китайское имя Размер занимаемой площади Краткое описание
File Header заголовок файла 38 байт Немного общей информации на странице
Page Header Заголовок страницы 56 байтов Некоторая информация, относящаяся к странице данных
Infimum + Supremum Минимальная запись и максимальная запись 26 байт две фиктивные записи строки
User Records Запись пользователя неуверенный Контент фактически хранится в строках
Free Space свободное место неуверенный неиспользуемое место на странице
Page Directory каталог страниц неуверенный Относительное положение некоторых записей на странице
File Trailer конец файла 8 байт Проверьте, заполнена ли страница

Хранение записей на страницах

Собственные сохраненные данные пользователя будут храниться в соответствии с行格式существуетUser Recordsсередина. На самом деле вновь сгенерированная страница неUser Records, только когда мы вставляем данные в первый раз, будетFree SpaceВыделить место рекордного размера дляUser Records. когдаFree SpaceПосле того, как он израсходован, это означает, что текущая страница данных также израсходована.记录在页中的存储чтобы иметь возможностьUser RecordsЧтобы прояснить, мы должны сначала понять вышеупомянутое记录头信息.

Информация о заголовке записи

Сначала кратко представим описание каждого атрибута информации заголовка записи:

название размер (единица измерения: бит) описывать
зарезервированный бит 1 1 не использовал
Зарезервированный слот 2. 1 не использовал
delete_mask 1 отметьте, удалена ли запись
min_rec_mask 1 Эта метка будет добавлена ​​к наименьшей записи в нелистовом узле каждого уровня дерева B+.
n_owned 4 Указывает количество записей, принадлежащих текущей записи
heap_no 13 Указывает информацию о местоположении, записанную в настоящее время в куче записей.
record_type 3 Указывает тип текущей записи, 0 представляет собой обычную запись, 1 представляет собой запись неконечного узла дерева B+, 2 представляет наименьшую запись, а 3 представляет наибольшую запись.
next_record 16 Представляет относительное положение следующей записи

Далее сpage_demoТаблица в качестве примера и вставьте некоторые данные, чтобы подробно описать информацию заголовка записи.

mysql> CREATE TABLE page_demo(
    ->     c1 INT,
    ->     c2 INT,
    ->     c3 VARCHAR(10000),
    ->     PRIMARY KEY (c1)
    -> ) CHARSET=ascii ROW_FORMAT=Compact;
Query OK, 0 rows affected (0.03 sec)

mysql> INSERT INTO page_demo VALUES(1, 100, 'aaaa'), (2, 200, 'bbbb'), (3, 300, 'cccc'), (4, 400, 'dddd');
Query OK, 4 rows affected (0.00 sec)
Records: 4  Duplicates: 0  Warnings: 0

Формат строки этих четырех записей в InnoDB следующий (отображается только заголовок записи и реальные данные), а все данные в столбцах выражены в десятичном формате:记录头Сосредоточимся на деталях нескольких свойств по отношению к этой фигуре:

  • delete_mask: Отмечает, удалена ли текущая запись, 0 означает, что запись не удалена, 1 означает, что она удалена. Неудаленные записи не будут удалены с диска сразу, а сначала будут помечены для удаления, а все удаленные записи сформируют垃圾链表. Вновь вставленные записи могут быть повторно использованы позже垃圾链表Занятое пространство, поэтому пространство для хранения, занимаемое списком мусора, также называется可重用空间.
  • heap_no: указывает положение текущей записи на этой странице.Например, позиции четырех записей выше на этой странице:2、3、4、5. Фактически, InnoDB автоматически добавит две виртуальные записи на каждую страницу, одна из которых最小记录, другой最大记录. Конструкция этих двух записей очень проста.5字节大小的记录头信息и8字节大小的固定部分(На самом деле содержание состоит из инфимума или супремума). Эти две записи размещаются отдельноInfimum + Supremumчасть.heap_noИз рисунка видно, что минимальная и максимальная записиheap_noЗначения равны 0 и 1 соответственно, что означает, что они находятся впереди.
  • next_record: означает изАдресное смещение реальных данных текущей записи к реальным данным следующей записи. Его можно просто понимать как односвязный список, следующая запись наименьшей записи является первой записью, а следующая из последней записи является самой большой записью. Для более наглядного отображения мы можем использовать стрелки для замены смещения адреса в следующей_записи:next_recordКак видно из рисунка,Пользовательские записи фактически сортируются в положительном порядке в соответствии с размером первичного ключа в односвязный список.. Если удалить из него запись, то изменится и связанный список.Например удаляем вторую запись:next_record
    • Вторая запись не удаляется из хранилища, но записьdelete_maskЗначение устанавливается равным 1.
    • Статья 2 протоколаnext_recordЗначение становится равным 0, что означает, что для этой записи нет следующей записи.
    • Статья 1 протоколаnext_recordУказывает на 3-ю запись.

Каталог страниц

Мы уже знаем, что записи объединяются в односвязный список в положительном порядке размера первичного ключа на странице. Что, если мы хотим найти конкретную запись по первичному ключу, самый простой способ — пройтись по связанному списку. Однако в случае относительно большого объема данных этот метод явно слишком неэффективен. Итак, mysql используетPage Directory(页目录)Для решения этой проблемы.Page Directory(页目录)Общий принцип таков:

  1. Разделите все обычные записи (включая самые большие и самые маленькие записи, исключая записи, помеченные как удаленные) на группы. Как разделить первое не обращайте внимания.
  2. Атрибут n_owned в заголовке последней записи каждой группы (т. е. самой большой записи в группе) указывает количество записей в группе.
  3. Извлеките смещение адреса последней записи каждой группы отдельно и сохраните его по порядку ближе к концу страницы.Это место называетсяPage Directory.

MySQL предусматривает, что может быть только 1 запись для группы, в которой находится самая маленькая запись, количество записей в группе, где находится самая большая запись, может быть только между 1-8, а количество записей в остальных группах может быть быть только в диапазоне от 4 до 8. Например, текущийpage_demoВ таблице 18 обычных записей, и InnoDB разделит их на группы 5. В первой группе есть только одна минимальная запись, как показано ниже:Page Directory

пройти черезPage DirectoryПроцесс поиска записи с указанным значением первичного ключа на странице данных делится на два этапа:

  1. Определите слот, в котором находится запись, с помощью дихотомии и найдите запись с наименьшим значением первичного ключа в группе, где находится слот.
  2. по записиnext_recordСвойство перебирает записи в группе, в которой находится слот.

Для оптимизации производительности запроса связанного списка идея в основном заключается в二分法осуществленный. описано вышеPage Directory,跳跃表и查找树Это все так.

Заголовок страницы

Page HeaderОн специально используется для хранения различной информации о состоянии, связанной со страницами данных, например, сколько записей было сохранено на этой странице, каков адрес первой записи, сколько слотов хранится в каталоге страниц и так далее. Он занимает фиксированно 56 байт, а значения байтовых атрибутов каждой части следующие:

название Размер занимаемой площади описывать
PAGE_N_DIR_SLOTS 2 байта Количество слотов в каталоге страниц
PAGE_HEAP_TOP 2 байта Наименьший адрес неиспользованного пространства, то есть после этого адресаFree Space
PAGE_N_HEAP 2 байта Количество записей на этой странице (включая минимальное и максимальное количество записей и записей, помеченных для удаления)
PAGE_FREE 2 байта Адрес первой записи, помеченной для удаления (каждая удаленная запись также будет формировать односвязный список через next_record, и записи в этом односвязном списке можно использовать повторно)
PAGE_GARBAGE 2 байта Количество байтов, занимаемых удаленными записями
PAGE_LAST_INSERT 2 байта Позиция последней вставленной записи
PAGE_DIRECTION 2 байта Направление, в котором была вставлена ​​последняя запись
PAGE_N_DIRECTION 2 байта Количество записей, непрерывно вставляемых в одном направлении. Если изменить направление вставки последней записи, значение этого состояния будет очищено и подсчитано заново.
PAGE_N_RECS 2 байта Количество записей на этой странице (исключая минимальные и максимальные записи и записи, помеченные для удаления)
PAGE_MAX_TRX_ID 8 байт Измените максимальный идентификатор транзакции текущей страницы, это значение определяется только во вторичном индексе.
PAGE_LEVEL 2 байта Уровень текущей страницы в дереве B+
PAGE_INDEX_ID 8 байт Идентификатор индекса, указывающий, к какому индексу принадлежит текущая страница.
PAGE_BTR_SEG_LEAF 10 байт Информация заголовка дочернего сегмента дерева B+ определяется только на корневой странице дерева B+.
PAGE_BTR_SEG_TOP 10 байт Информация заголовка неконечного сегмента дерева B+ определяется только на корневой странице дерева B+.

Это только перечислено здесь, и пока нет необходимости понимать их все.

Заголовок файла

File Headerиспользуется для описания некоторой общей информации, применимой к различным страницам, и состоит из следующего:

название Размер занимаемой площади описывать
FIL_PAGE_SPACE_OR_CHKSUM 4 байта Контрольная сумма страницы (значение контрольной суммы)
FIL_PAGE_OFFSET 4 байта номер страницы
FIL_PAGE_PREV 4 байта номер предыдущей страницы
FIL_PAGE_NEXT 4 байта номер следующей страницы
FIL_PAGE_LSN 8 байт Соответствующая позиция в последовательности журнала при последнем изменении страницы (английское название: Log Sequence Number)
FIL_PAGE_TYPE 2 байта тип страницы
FIL_PAGE_FILE_FLUSH_LSN 8 байт Определен только на одной странице системного табличного пространства, что означает, что файл был сброшен как минимум до соответствующего значения LSN.
FIL_PAGE_ARCH_LOG_NO_OR_SPACE_ID 4 байта к какому табличному пространству принадлежит страница

Это только перечислено здесь, и пока нет необходимости понимать их все. Мы сосредоточимся на нескольких свойствах:

  1. FIL_PAGE_SPACE_OR_CHKSUM

Контрольная сумма текущей страницы. Для очень длинной строки байтов мы можем вычислить относительно короткое значение, чтобы представить эту очень длинную строку байтов с помощью некоторого алгоритма, и это относительно короткое значение называется校验和. пройти через校验和Это может значительно повысить эффективность сравнения строк на равенство. 2.FIL_PAGE_OFFSETКаждая страница имеет уникальный номер страницы,InnoDB通过页号来可以定位一个页。 3.FIL_PAGE_TYPEПредставляет тип текущей страницы Как мы уже говорили ранее, InnoDB делит страницы на разные типы для разных целей.

имя типа шестигранник описывать
FIL_PAGE_TYPE_ALLOCATED 0x0000 Последнее распределение, еще не использовано
FIL_PAGE_TYPE_ALLOCATED 0x0000 Последнее распределение, еще не использовано
FIL_PAGE_UNDO_LOG 0x0002 Отменить страницу журнала
FIL_PAGE_INODE 0x0003 информационный узел сегмента
FIL_PAGE_IBUF_FREE_LIST 0x0004 Вставить список свободных буферов
FIL_PAGE_IBUF_BITMAP 0x0005 Вставить растровое изображение буфера
FIL_PAGE_TYPE_SYS 0x0006 системная страница
FIL_PAGE_TYPE_TRX_SYS 0x0007 данные системы транзакций
FIL_PAGE_TYPE_FSP_HDR 0x0008 Информация о заголовке табличного пространства
FIL_PAGE_TYPE_XDES 0x0009 Страница расширенного описания
FIL_PAGE_TYPE_BLOB 0x000A страница переполнения
FIL_PAGE_INDEX 0x45BF Страницы индекса, также известные как страницы данных
4. FIL_PAGE_PREVи ФIL_PAGE_NEXT
Указывает номер страницы предыдущей и следующей страницы этой страницы, каждая страница пропускаетсяFIL_PAGE_PREVи ФIL_PAGE_NEXTСформируйте двусвязный список.
FIL_PAGE_PREV

File Trailer

Основной единицей взаимодействия между памятью и диском в MySQL является страница. Если страница в памяти изменена, страница памяти должна быть синхронизирована с диском в какой-то момент. Если в процессе синхронизации возникает проблема с системой, это может привести к тому, что данные страницы на диске не будут полностью синхронизированы, т.脏页Случай. Чтобы избежать этой проблемы, mysql добавляет в конце каждой страницыFile Trailerдля проверки целостности страницы.File TrailerСостоит из 8 байт:

  1. Первые 4 байта представляют контрольную сумму страницы Этот раздел соответствует контрольному заказу в заголовке файла. Простое понимание,File HeaderиFile TrailerУ обоих есть контрольные суммы, и если они совпадают, страница данных завершена. В противном случае страница данных脏页.
  2. Последние 4 байта представляют собой позицию последовательности журнала (LSN), соответствующую последней модификации страницы. Эта часть также предназначена для проверки целостности страницы, поэтому я не знаю ее в деталях.

Нелегко быть оригинальным. Если вы считаете, что статья написана хорошо, пожалуйста, поставьте лайк 👍 и поддержите ее~

Добро пожаловать в мой проект с открытым исходным кодом:Облегченная среда вызовов HTTP для SpringBoot