Анализ исходного кода MySQL 8.0 MVCC

опрос

предисловие

прежде чем вMySQL нужно спрашивать на собеседованиях, понимаете?В книге кратко представлен принцип MVCC, освоение которого действительно может добавить много очков во время собеседования.

Поскольку понимание многих людей все еще остается в версии из книги "High Performance MySQL", то есть черезСоздать номер версииа такжеудалить номер версиисудить. В это время, если вы можете дать правильное понимание, это заставит глаза интервьюера сиять.Вот почему мы подчеркиваем «разницу между нами и другими кандидатами» в интервью, что будет больше способствовать выделению среди многих кандидатов. . . .

Исходя из этого, в данной статье проводится подробный анализ MVCC, а заодно пересматриваются некоторые неточные утверждения ранее, в надежде помочь вам лучше развить (чжуан) и сыграть (би) на собеседовании.

PS: Исходный код этой статьи основан на MySQL 8.0.16.Для версии 5.7.*, обычно используемой в производственной среде на данном этапе, исходный код части MVCC в основном такой же, поэтому вы можете смело обращаться к Это. 5.6.* сильно отличается, главным образом потому, что изменились некоторые структуры данных, но основные принципы остались теми же.

Основные понятия

Проблемы (явления), вызванные одновременными транзакциями

Грязное чтение: транзакция считывает данные, обновленные другой транзакцией, но еще не зафиксированные.Если другая транзакция откатится или обновит данные, возникнут проблемы.

Неповторяющееся чтение: когда одни и те же данные считываются дважды в транзакции, результаты двух чтений несовместимы, поскольку другая транзакция изменяет данные между двумя чтениями.

Фантомное чтение: один и тот же SQL используется для чтения дважды в транзакции, а во второй раз считывается вновь вставленная строка другой транзакцией.

Чтобы решить проблемы, вызванные этими параллельными транзакциями, относительно простым и грубым методом является блокировка, но блокировка неизбежно снижает производительность, поэтому MySQL использует MVCC для повышения производительности при параллельных транзакциях.

Преимущества MVCC?

Представьте, если нет MVCC, то для обеспечения безопасности параллельных транзакций относительно простой способ придумать — добавить блокировки чтения-записи для достижения: чтение-чтение без конфликтов, чтение-запись конфликта, запись-чтение конфликт, конфликт записи-записи, в данном случае производительность параллельных операций чтения и записи серьезно пострадает.

А через MVCC можно добиться отсутствия конфликта между чтением и записью, при чтении нам нужно только скопировать текущую запись в память (ReadView), и тогда запрос транзакции будет иметь дело только с ReadView, не затрагивая другие транзакции. Напишите операции для этой записи.

Уровень изоляции транзакции

Чтение незафиксированного (Read Uncommitted): самый низкий уровень изоляции, он будет считывать содержимое, которое еще не зафиксировано другими транзакциями, и есть грязные чтения.

Чтение зафиксировано: прочитанное содержимое было зафиксировано, что может решить проблему грязного чтения, но есть неповторяющиеся чтения.

Повторяющееся чтение: просмотр одного и того же содержимого при многократном чтении в транзакции может решить неповторяющееся чтение, но существует фантомное чтение. Однако в InnoDB нет проблемы фантомного чтения.Для чтения моментальных снимков InnoDB использует MVCC для решения фантомных чтений.Для текущих чтений InnoDB решает фантомные чтения с помощью блокировок пробелов или блокировок следующего ключа.

Сериализуемый: самый высокий уровень изоляции, последовательное выполнение транзакций, отсутствие проблем с одновременными транзакциями.

Реализация InnoDB MVCC

основная структура данных

trx_sys_t: структура данных центральной памяти системы транзакций

struct trx_sys_t {
  TrxSysMutex mutex; /*! 互斥锁 */
​
  MVCC *mvcc;    /*!  mvcc */
​
  volatile trx_id_t max_trx_id; /*! 要分配给下一个事务的事务id*/
​
  std::atomic<trx_id_t> min_active_id; /*! 最小的活跃事务Id */
  
  // 省略...
​
  trx_id_t rw_max_trx_id; /*!< 最大读写事务Id */
​
  // 省略...
​
  trx_ids_t rw_trx_ids; /*! 当前活跃的读写事务Id列表 */
​
  Rsegs rsegs; /*!< 回滚段 */
​
  // 省略...
};

MVCC: менеджер представления чтения MVCC

class MVCC {
 public:
  // 省略...
​
  /** 创建一个视图 */
  void view_open(ReadView *&view, trx_t *trx);
​
  /** 关闭一个视图 */
  void view_close(ReadView *&view, bool own_mutex);
​
  /** 释放一个视图 */
  void view_release(ReadView *&view);
​
 // 省略...
​
  /** 判断视图是否处于活动和有效状态 */
  static bool is_view_active(ReadView *view) {
    ut_a(view != reinterpret_cast<ReadView *>(0x1));
​
    return (view != NULL && !(intptr_t(view) & 0x1));
  }
​
 // 省略...
​
 private:
  typedef UT_LIST_BASE_NODE_T(ReadView) view_list_t;
​
  /** 空闲可以被重用的视图*/
  view_list_t m_free;
​
  /**  活跃或者已经关闭的 Read View 的链表 */
  view_list_t m_views;
};

ReadView: просмотр, моментальный снимок транзакции за раз

class ReadView {
​
  // 省略...
​
 private:
  /** 高水位,大于等于这个ID的事务均不可见*/
  trx_id_t m_low_limit_id;
​
  /** 低水位:小于这个ID的事务均可见 */
  trx_id_t m_up_limit_id;
​
  /** 创建该 Read View 的事务ID*/
  trx_id_t m_creator_trx_id;
​
  /** 创建视图时的活跃事务id列表*/
  ids_t m_ids;
​
  /** 配合purge,标识该视图不需要小于m_low_limit_no的UNDO LOG,
   * 如果其他视图也不需要,则可以删除小于m_low_limit_no的UNDO LOG*/
  trx_id_t m_low_limit_no;
​
  /** 标记视图是否被关闭*/
  bool m_closed;
​
  // 省略...
};

Добавить скрытые поля

Для реализации MVCC InnoDB добавляет три поля в каждую строку базы данных:

DB_ROW_ID: идентификатор строки, 6 байт, монотонно увеличивающийся по мере вставки новых строк, этот столбец не будет включен, если есть первичный ключ.

DB_TRX_ID: идентификатор транзакции, 6 байт, идентификатор транзакции последней транзакции, которая записывает вставку или обновление строки, то есть идентификатор транзакции.

DB_ROLL_PTR: указатель отката, 7 байт, указывает на запись журнала отмены, записанную в сегмент отката. Каждый раз, когда запись обновляется, содержимое строки перед обновлением будет записано в журнал отмены, а обновленная запись строки будет указывать на журнал отмены через DB_ROLL_PTR. Когда запись изменяется несколько раз, будет несколько версий записи строки, и через ссылку DB_ROLL_PTR формируется концепция, аналогичная цепочке версий, как показано на рисунке ниже.

Анализ исходного кода

В исходном коде метод добавления этих трех полей находится в методе dict_table_add_system_columns файла /storage/innobase/dict/dict0dict.cc Основная часть выглядит следующим образом.

Базовые операции добавления, удаления и модификации

Когда мы обновляем часть данных, InnoDB сделает следующее:

  1. Блокировка: добавьте эксклюзивную блокировку к записи строки, которую нужно обновить.

  2. Запись журнала отмены: запишите запись перед обновлением в журнал отмены и создайте указатель отката roll_ptr, указывающий на журнал отмены.

  3. Обновить запись строки: обновить атрибут DB_TRX_ID записи строки до текущего идентификатора транзакции, обновить атрибут DB_ROLL_PTR до указателя отката, созданного на шаге 2, и обновить столбец атрибута до целевого значения.

  4. Запись журнала повторов: DB_ROLL_PTR использует указатель отката, сгенерированный на шаге 2, DB_TRX_ID использует идентификатор текущей транзакции и заполняет обновленное значение атрибута.

  5. Когда процесс завершится, снимите монопольную блокировку

Операция удаления: в базовой реализации она реализована с использованием обновления. Логика в основном такая же, как и операция обновления. Несколько моментов, на которые следует обратить внимание: 1) при записи журнала отмены будет использоваться type_cmpl для определения, следует ли удалить или обновить, и столбец не будет записываться старое значение; 2) Непосредственно здесь он не будет удален, только info_bits записи строки будут помечены флагом удаления (REC_INFO_DELETED_FLAG), а затем выделенный поток очистки выполнит настоящую удалить операцию.

Операция вставки: по сравнению с операцией обновления добавление записи проще, DB_TRX_ID использует идентификатор текущей транзакции, а также будет журнал отмен и журнал повторов.

Анализ исходного кода

Основной исходный код для обновления записей строк находится в методе /storage/innobase/btr/btr0cur.cc/btr_cur_update_in_place, основная часть показана ниже.

Построение согласованного представления для чтения (ReadView)

Когда наш уровень изоляции RR: каждый раз, когда транзакция открывается, система будет назначать транзакции идентификатор транзакции.Когда транзакция выполняет первый оператор выбора, будет сгенерирован снимок транзакции ReadView в текущий момент времени. атрибут выглядит следующим образом:

  • m_ids: список идентификаторов транзакций, которые активны в текущей системе при создании ReadView, что можно понимать как транзакции, которые еще не были зафиксированы на момент создания ReadView, и этот список представляет собой восходящий список.

  • m_up_limit_id: низкий уровень воды, возьмите первый узел списка m_ids, потому что m_ids — это восходящий список, поэтому он имеет наименьший идентификатор транзакции в m_ids.

  • m_low_limit_id: максимальная отметка, значение идентификатора, которое система назначит следующей транзакции при создании ReadView.

  • m_creator_trx_id: идентификатор транзакции, создавшей этот ReadView.

Анализ исходного кода

Основная запись метода для общего запроса в режиме MVCC находится в методе row_search_mvcc в файле /storage/innobase/row/row0sel.cc, и весь последующий анализ исходного кода в основном выполняется в этом методе.

Конкретный метод создания представления находится в ReadView::prepare, а цепочка вызовов выглядит следующим образом:

row_search_mvcc -> trx_assign_read_view -> MVCC::view_open ->

ReadView::prepare, исходный код выглядит следующим образом:

Наконец, этот созданный ReadView добавляется к m_views MVCC.

Представление оценки видимости: SQL-запрос проходит по кластеризованному индексу

С помощью этого ReadView при доступе к записи вам нужно всего лишь выполнить следующие шаги, чтобы определить, видна ли версия записи:

  1. Если trx_id доступной версии совпадает со значением m_creator_trx_id в ReadView, это означает, что текущая транзакция обращается к своей измененной записи, поэтому эта версия может быть доступна текущей транзакции.

  2. Если trx_id доступной версии меньше, чем m_up_limit_id (нижняя отметка) в ReadView, это указывает на то, что транзакция доступной версии была зафиксирована до того, как текущая транзакция сгенерирует ReadView, поэтому эта версия может быть доступна текущей транзакции. .

  3. Если trx_id версии, к которой осуществляется доступ, больше или равен m_low_limit_id (высокий уровень воды) в ReadView, это указывает на то, что транзакция версии, к которой осуществляется доступ, открывается после того, как текущая транзакция генерирует ReadView, поэтому текущая транзакция не может получить доступ к этой версии. .

  4. Если значение атрибута trx_id доступной версии находится между m_up_limit_id и m_low_limit_id ReadView, необходимо определить, находится ли значение атрибута trx_id в списке m_ids, который здесь будет искаться по дихотомии. Если это так, это означает, что транзакция, сгенерировавшая эту версию, все еще активна при создании ReadView, и доступ к версии невозможен; если это не так, это означает, что транзакция, сгенерировавшая версию при создании ReadView, была завершена. зафиксировано, и версия может быть доступна.

При принятии решения сначала будет сравниваться последняя версия записи.Если версия не видна текущей транзакцией, предыдущая версия будет найдена через записанный DB_ROLL_PTR, и сравнение будет выполняться снова, пока не будет найдена версия, которая может быть видно, текущая транзакция найдена.

Для удаления это на самом деле специальное обновление InnoDB использует флаговый бит delete_flag в info_bits, чтобы определить, следует ли удалять. При оценке мы проверим, отмечен ли флаг удаления, и если да, то обработаем его по ситуации: 1) Если индекс является кластерным индексом и имеет уникальные характеристики (первичный ключ, уникальный индекс и т. , он вернет DB_RECORD_NOT_FOUND; 2) В противном случае он будет искать следующую запись, чтобы продолжить процесс.

На самом деле это легко понять. Если это запрос уникального индекса, должна быть только одна запись. Если он удален, он сразу же вернет пустой. Если это общий индекс, может быть несколько записей строк с то же значение. Если строки не существует, продолжайте поиск.

Вышеупомянутое содержимое относится к уровню RR, а для уровня RC весь процесс почти такой же, единственное отличие заключается во времени генерации ReadView.Уровень RR генерируется только один раз, когда транзакция выбрана в первый раз. , и с тех пор используется ReadView. Уровень RC генерирует ReadView каждый раз, когда он выбран.

Анализ исходного кода

Основной процесс обхода кластеризованного индекса находится в методе row_search_mvcc следующим образом:

Видимость представления оценивается в методе: changes_visible, а цепочка вызовов выглядит следующим образом:

row_search_mvcc -> lock_clust_rec_cons_read_sees ->

changes_visible, исходный код выглядит следующим образом:

Метод определения того, отмечена ли запись флагом delete_flag, находится в методе rec_get_deleted_flag файла /storage/innobase/include/rem0rec.ic, как показано на рисунке ниже.

Получить предыдущую версию записи

Предыдущая версия записи получается в основном через DB_ROLL_PTR.Основной процесс выглядит следующим образом:

  1. Получить указатель отката записи DB_ROLL_PTR, получить идентификатор транзакции записи

  2. Получить соответствующий журнал отмены через указатель отката

  3. Проанализируйте журнал отмены и используйте журнал отмены для создания вектора UPDATE для обновления.

  4. Создайте предыдущую версию записи: сначала заполните текущую версию записи, затем используйте UPDATE (журнал отмены) для перезаписи.

Анализ исходного кода

Предыдущая версия записи сборки: trx_undo_prev_version_build, цепочка вызовов следующая:

row_search_mvcc -> row_sel_build_prev_vers_for_mysql -> row_vers_build_for_consistent_read -> trx_undo_prev_version_build, исходный код выглядит следующим образом:

Просмотр оценки видимости: SQL-запросы следуют обычным (вторичным) индексам

MySQL нужно спрашивать на собеседованиях, понимаете?Анализируется только случай использования кластерного индекса, в данной статье кратко представлен случай использования обычного (вторичного) индекса.

При использовании общего индекса логика оценки выглядит следующим образом:

  1. Определите, меньше ли максимальный идентификатор транзакции страницы, на которой находится доступная запись индекса, чем m_up_limit_id (низкий уровень воды) в ReadView. Если это так, это означает, что последний измененный идентификатор транзакции страницы был отправлен перед созданием ReadView, поэтому он должен быть доступен; если нет, это не означает, что к нему нельзя получить доступ. Причина та же, что и при использовании кластеризованного индекса. Транзакции с большими идентификаторами транзакций могут быть отправлены раньше, поэтому требуется дополнительное суждение, см. шаг 2.

  2. Используйте ICP (Index Condition Pushdown), чтобы оценить, удовлетворяются ли условия поиска в соответствии с информацией об индексе. Здесь мы в основном фильтруем перед использованием кластеризованного индекса для оценки. Здесь есть три ситуации: а) ICP оценивает, что условия не выполнены но не превышает диапазон сканирования, то получить следующую запись для продолжения поиска; б) если условие не выполнено и сканирование превышено, вернуть DB_RECORD_NOT_FOUND; в) если ICP считает, что условие выполнено, соответствующий кластеризованный индекс будет получено для оценки видимости.

Анализ исходного кода

Оценка видимости представления обычных (некластеризованных) индексов находится в методе: lock_sec_rec_cons_read_sees, а цепочка вызовов выглядит следующим образом:

row_search_mvcc -> lock_sec_rec_cons_read_sees, исходный код выглядит следующим образом:

Расширить понимание

ICP (проталкивание состояния индекса)

ICP — это оптимизация, представленная в MySQL 5.6. Согласно официальному заявлению: ICP может уменьшить количество обращений механизма хранения к базовой таблице и количество обращений MySQL к механизму хранения.Это включает в себя базовую логику обработки MySQL, что не является предметом рассмотрения в этой статье. Подробнее.

Вот краткое введение с официальным примером.У нас есть таблица людей, и индекс определяется как: ИНДЕКС (почтовый индекс, фамилия, имя), для следующего SQL:

SELECT * FROM people
  WHERE zipcode='95054'
  AND lastname LIKE '%etrunia%'
  AND address LIKE '%Main Street%';

Когда ICP не используется: этот запрос будет использовать индекс, но должен сканировать таблицу людей на наличие всех записей, соответствующих условию zipcode='95054'.

При использовании ICP: для фильтрации будет использоваться не только условие почтового индекса, но также (фамилия LIKE '%etrunia%') для фильтрации, что позволяет избежать сканирования записей, которые соответствуют условию почтового индекса, но не условию фамилии.

Официальная документация ICP:Dev.MySQL.com/doc/Furious/…

Текущее чтение и чтение моментального снимка

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

Официальная документация:Dev.MySQL.com/doc/Furious/…

Чтение моментального снимка: официально называется последовательным неблокирующим чтением (последовательное неблокирующее чтение, также называемое последовательным чтением), чтение версии моментального снимка, которая представляет собой ReadView, сгенерированный MVCC. Операторы для обычного выбора.

Официальная документация:Dev.MySQL.com/doc/Furious/…

Решил ли MVCC фантомное чтение?

MVCC решает некоторые фантомные чтения, но не полностью.

При чтении моментального снимка MVCC не увидит только что вставленную строку, потому что он читает из ReadView, поэтому естественно решает проблему фантомного чтения.

Для фантомного чтения текущего чтения MVCC не может быть разрешен. Для решения необходимо использовать Gap Lock или Next-Key Lock (Gap Lock + Record Lock).

На самом деле, принцип также очень прост.Используйте приведенный выше пример, чтобы немного изменить, чтобы запустить текущее чтение: выберите * от пользователя, где id

Repeatable Read решает, что такое фантомное чтение?

RR, указанный в стандарте SQL, не может устранить фантомные чтения, но RR MySQL InnoDB может, полагаясь на блокировки Gap. На уровне RR блокировка промежутка включена по умолчанию, а на уровне RC блокировка промежутка отключена.

несколько примеров

Пример 1: Когда RR(RC) фактически генерирует ReadView

Анализ: время, когда RR создает ReadView, — это когда транзакция сначала выбирает, а не когда транзакция начинается. В примере справа транзакция 1 выполняет первый выбор после того, как транзакция 2 зафиксирует модификацию, поэтому в сгенерированном ReadView a равно 100 вместо 50 в начале транзакции 1.

Пример 2: Разница между RR и RC, генерирующими ReadView

Разрешение: уровень RR создается только один раз, когда транзакция выбирается в первый раз, и с тех пор используется ReadView. Уровень RC генерирует ReadView каждый раз при выборе, поэтому значение модификации транзакции 2 для a считывается при втором выборе.

наконец-то

Исходный код MySQL в основном написан на C++, поэтому я, кажется, борюсь, и на его изучение и организацию ушло много времени. Если вы сможете освоить содержание этой статьи и пройти собеседование на должность Java, независимо от того, в какой компании вы работаете, я верю, что глаза интервьюера засияют.

Сейчас конкуренция в интернете становится все более и более жесткой.Если многое останется только на поверхности, будет трудно заполучить «сердце» интервьюера.Только когда вы показываете свой «длинный меч» в нужный момент Вы выделяетесь среди многих кандидатов. Ваш собственный отличается. Вы должны продемонстрировать интервьюеру, почему это вы, а не кто-то другой.

Рекомендуемое чтение

921 день, от небольшой фабрики до присоединения к Alibaba

Резюме собеседования по двухлетнему опыту работы в сфере Java-разработки

4 года опыта работы с Java, резюме интервью Ali Netease Pinduoduo, опыт

5-летний опыт работы с Java, резюме интервью с основными отделами Byte, Meituan и Kuaishou (анализ реальных вопросов)

Интервью с Али, достаточно HashMap

Вы понимаете CAS, который необходимо задать на собеседовании?

После 2 месяцев рассмотрения я выиграл предложение от Meituan, что я сделал?

Пул потоков, который нужно задавать на собеседовании, понимаете?

MySQL нужно спрашивать на собеседованиях, понимаете?

Весну надо спрашивать в интервью, понимаешь?

Автор: Программист Джон Хуэй
Связь:Наггетс.Талант/пост/694990…
Источник: Самородки
Авторские права принадлежат автору. Для коммерческих перепечаток, пожалуйста, свяжитесь с автором для получения разрешения, а для некоммерческих перепечаток, пожалуйста, укажите источник.