Чем в базе данных представляется информация

Представления

Представление — это виртуальная таблица, содержимое которой определяется запросом. Как и таблица, представление состоит из ряда именованных столбцов и строк данных. Пока представление не будет проиндексировано, оно не существует в базе данных как хранимая совокупность значений. Строки и столбцы данных извлекаются из таблиц, указанных в определяющем представление запросе и динамически создаваемых при обращениях к представлению.

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

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

Типы представлений

Кроме основных определяемых пользователем представлений, выполняющих стандартные роли, в SQL Server предусмотрены следующие типы представлений, которые соответствуют специальным назначениям в базе данных.

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

Системные представления
Системные представления предоставляют доступ к метаданным каталога. Системные представления можно использовать для получения сведений об экземпляре SQL Server или объектах, определенных в экземпляре. Например, получить сведения об определяемых пользователем базах данных, доступных в экземпляре, можно через представление каталога sys.databases. Дополнительные сведения см. в разделе Системные представления (Transact-SQL).

Общие задачи работы с представлениями

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

Источник

Представление (базы данных)

В отличие от обычных таблиц реляционной БД, представление не является самостоятельной частью набора данных, хранящегося в базе. Содержимое представления динамически вычисляется на основании данных, находящихся в реальных таблицах. Изменение данных в реальной таблице БД немедленно отражается в содержимом всех представлений, построенных на основании этой таблицы.

Содержание

Способ создания и содержимое представлений

Типичным способом создания представлений для СУБД, поддерживающих язык запросов SQL, является связывание представления с определённым SQL-запросом. Соответственно, содержимое представления — это результат выполнения этого запроса, а возможности построения представления ограничиваются только степенью сложности диалекта SQL, поддерживаемого конкретной СУБД. Так, для типичных СУБД, таких как PostgreSQL, Interbase, Firebird, Microsoft SQL Server, Oracle, представление может содержать:

Использование

Представления используются в запросах к БД тем же образом, как и обычные таблицы. В случае SQL-СУБД имя представления может находиться в SQL-запросе на месте имени таблицы (в предложении FROM). Запрос из представления обрабатывается СУБД точно так же, как запрос, в котором на месте имени представления находится подзапрос, определяющий это представление. При этом СУБД с развитыми возможностями оптимизации запросов перед выполнением запроса из представления могут проводить совместную оптимизацию запроса верхнего уровня и запроса, определяющего представление, с целью минимизации затрат на выборку данных.

Использование представлений не даёт каких-то совершенно новых возможностей в работе с БД, но может быть очень удобно.

Специфические типы представлений

Некоторые СУБД имеют расширенные представления для данных, доступных только для чтения. Так, СУБД Oracle реализует концепцию «материализованных представлений» — представлений, содержащих предварительно выбранные невиртуальные наборы данных, совместно используемых в распределённых БД. Эти данные извлекаются из различных удалённых источников (с разных серверов распределённой СУБД). Целостность данных в материализованных представлениях поддерживается за счёт периодических синхронизаций или с использованием триггеров. Аналогичный механизм предусмотрен в Microsoft SQL Server версии 2000.

По самой сути представления могут быть доступны только для чтения. Тем не менее, в некоторых СУБД (например, в Oracle) представления могут быть редактируемыми, как и обычные физические таблицы. Редактирование может допускаться для представлений, выбранных из единственной физической таблицы таким образом, чтобы каждой записи в представлении соответствовала строго одна запись в таблице-источнике, а в числе полей представления был первичный ключ физической таблицы. При выполнении команд редактирования, добавления или удаления для такого представления сервер СУБД преобразует эти команды в соответствующие команды для физической таблицы-источника. Разумеется, если в представлении используется группировка записей или преобразование значений в полях, редактирование такого представления невозможно даже теоретически. Но и такие представления могут, тем не менее, редактироваться, посредством написания соответствующих триггеров (хотя осмысленность подобных операций целиком останется на совести программиста). Впрочем, редактируемые представления, как и возможность создания триггеров для представлений, поддерживают лишь немногие СУБД.

Источник

Представления (VIEW) в MySQL

В комментариях Хабра упоминались вопросы по использованию представлений. Данный топик является обзором представлений, появившихся в MySQL версии 5.0. В нем рассмотрены вопросы создания, преимущества и ограничения представлений.

Что такое представление?

Представление (VIEW) — объект базы данных, являющийся результатом выполнения запроса к базе данных, определенного с помощью оператора SELECT, в момент обращения к представлению.

Представления иногда называют «виртуальными таблицами». Такое название связано с тем, что представление доступно для пользователя как таблица, но само оно не содержит данных, а извлекает их из таблиц в момент обращения к нему. Если данные изменены в базовой таблице, то пользователь получит актуальные данные при обращении к представлению, использующему данную таблицу; кэширования результатов выборки из таблицы при работе представлений не производится. При этом, механизм кэширования запросов (query cache) работает на уровне запросов пользователя безотносительно к тому, обращается ли пользователь к таблицам или представлениям.

Представления могут основываться как на таблицах, так и на других представлениях, т.е. могут быть вложенными (до 32 уровней вложенности).

Преимущества использования представлений:

Ограничения представлений в MySQL

Создание представлений

view_name — имя создаваемого представления. select_statement — оператор SELECT, выбирающий данные из таблиц и/или других представлений, которые будут содержаться в представлении

CREATE VIEW v AS SELECT a.id, b.id FROM a,b;

CREATE VIEW v (a_id, b_id) AS SELECT a.id, b.id FROM a,b;

CREATE VIEW v AS SELECT a.id a_id, b.id b_id FROM a,b;

CREATE VIEW v AS SELECT group_concat( DISTINCT column_name oreder BY column_name separator ‘+’ ) FROM table_name;

Алгоритмы представлений

Существует два алгоритма, используемых MySQL при обращении к представлению: MERGE и TEMPTABLE.

В случае алгоритма MERGE, MySQL при обращении к представлению добавляет в использующийся оператор соответствующие части из определения представления и выполняет получившийся оператор.

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

При создании представления есть возможность явно указать используемый алгоритм с помощью необязательной конструкции [ALGORITHM = ]
UNDEFINED означает, что MySQL сам выбирает какой алгоритм использовать при обращении к представлению. Это значение по умолчанию, если данная конструкция отсутствует.

Использование алгоритма MERGE требует соответствия 1 к 1 между строками таблицы и основанного на ней представления.

Пусть наше представление выбирает отношение числа просмотров к числу ответов для тем форума:

CREATE VIEW v AS SELECT subject, num_views/num_replies AS param FROM topics WHERE num_replies>0;

SELECT subject, param FROM v WHERE param>1000;

SELECT subject, num_views/num_replies AS param FROM topics WHERE num_replies>0 AND num_views/num_replies>1000;

Если в определении представления используются групповые функции (count, max, avg, group_concat и т.д.), подзапросы в части перечисления полей или конструкции DISTINCT, GROUP BY, то не выполняется требуемое алгоритмом MERGE соответствие 1 к 1 между строками таблицы и основанного на ней представления.

Пусть наше представление выбирает количество тем для каждого форума:

CREATE VIEW v AS SELECT forum_id, count (*) AS num FROM topics GROUP BY forum_id;

SELECT MAX ( count (*)) FROM topics GROUP BY forum_id;

Выполнение этого запроса приводит к ошибке «ERROR 1111 (HY000): Invalid USE of GROUP function», так как используется вложенность групповых функций.

В этом случае MySQL использует алгоритм TEMPTABLE, т.е. заносит содержимое представления во временную таблицу (данный процесс иногда называют «материализацией представления»), а затем вычисляет MAX() используя данные временной таблицы:

CREATE TEMPORARY TABLE tmp_table SELECT forum_id, count (*) AS num FROM topics GROUP BY forum_id;
SELECT MAX (num) FROM tmp_table;
DROP TABLE tpm_table;

Обновляемость представлений

Обновляемое представление может допускать добавление данных (INSERT), если все поля таблицы-источника, не присутствующие в представлении, имеют значения по умолчанию.

Обратите внимание: для представлений, основанных на нескольких таблицах, операция добавления данных (INSERT) работает только в случае если происходит добавление в единственную реальную таблицу. Удаление данных (DELETE) для таких представлений не поддерживается.

punbb > CREATE OR REPLACE VIEW v AS
-> SELECT forum_name, `subject`, num_views FROM topics,forums f
-> WHERE forum_id=f.id AND num_views>2000 WITH CHECK OPTION ;
Query OK, 0 rows affected (0.03 sec)

punbb > UPDATE v SET num_views=2003 WHERE subject= ‘test’ ;
Query OK, 0 rows affected (0.03 sec)
Rows matched: 1 Changed: 0 WARNINGS: 0

punbb > SELECT subject, num_views FROM topics WHERE subject= ‘test’ ;
+———+————+
| subject | num_views |
+———+————+
| test | 2003 |
+———+————+
1 rows IN SET (0.01 sec)

Однако, если мы попробуем установить значение num_views меньше 2000, то новое значение не будет удовлетворять условию WHERE num_views>2000 в определении представления и обновления не произойдет.

punbb > UPDATE v SET num_views=1999 WHERE subject= ‘test’ ;
ERROR 1369 (HY000): CHECK OPTION failed ‘punbb.v’

Не все обновляемые представления позволяют добавление данных:

Причина в том, что значением по умолчанию колонки forum_id является 0, поэтому добавляемая строка не удовлетворяет условию WHERE forum_id=f.id в определении представления. Указать же явно значение forum_id мы не можем, так как такого поля нет в определении представления:

punbb > INSERT INTO v (forum_name) VALUES ( ‘TEST’ );
Query OK, 1 row affected (0.00 sec)

Таким образом, наше представление, основанное на двух таблицах, позволяет обновлять обе таблицы и добавлять данные только в одну из них.

Источник

Как устроены базы данных

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

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

Тем не менее, теоретические знания иногда полезно освежить. Этим мы, в том числе, и займемся.

Чем в базе данных представляется информация. Смотреть фото Чем в базе данных представляется информация. Смотреть картинку Чем в базе данных представляется информация. Картинка про Чем в базе данных представляется информация. Фото Чем в базе данных представляется информация

О спикере: Илья Космодемьянский CEO и консультант в компании Data Egret, специалист по базам данных PostgreSQL, Oracle, DB2. А кроме того, отвечает за продвижение Postgres-технологий, выступает на конференциях и рассказывает людям, как с ними работать.

Ниже материал по докладу Ильи на РИТ++ 2017, который не был связан с какой-то конкретной базой данных, но охватывал многие основные аспекты.

Для чeго это нужно знать?

Хранение и обработка данных — mission-critical задача любой компьютерной системы.

Даже если у вас уже 30 лет есть блог в интернете на текстовых файлах, как у некоторых создателей баз данных бывает, все равно на самом деле этот текстовый файлик — это база данных, только очень простенькая.

Все пытаются изобрести базу данных. Один из докладчиков на конференции сказал: «20 лет назад я написал свою базу данных, только не знал, что это она!» Этот тренд в мире очень развит. Все стараются так делать.

Для работы с данными база данных — это очень удобная штука. Многие базы данных — это очень старые технологии. Они разрабатываются последние полвека, в 70-х годах уже были базы данных, которые работали по схожим принципам, что и сейчас.

Эти базы очень хорошо и продуманно написаны, поэтому теперь мы можем выбрать язык программирования и использовать общий удобный интерфейс обработки данных. Таким образом можно стандартизованно обрабатывать данные, не боясь, что они будут обработаны как-то по-другому.

При этом полезно помнить, что языки программирования меняются: вчера был Python 2, сегодня Python 3, завтра все побежали писать на Go, послезавтра еще на чем-то. У вас может быть кусок кода, который эмулирует работу по манипуляции с данными, которую, по идее, должна делать база данных, а вы не будете знать, что дальше с этим делать.

В большинстве баз данных интерфейс очень консервативный. Если взять PostgreSQL или Oracle, то с некоторым бубном можно работать даже с очень старыми версиями из новых языков программирования — хорошо и здорово.

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

Если вы попробуете написать свое простенькое персистентное хранилище, все просто будет только первые 15 минут. Потом начнутся блокировки и прочие вещи, и в какой-то момент вы поймете: «Ой, зачем я все это делаю?»

Об этом и поговорим.

Уровни работы с данными

Итак, есть различные уровни работы с данными:

Для слоя доступа к данным есть требования, в выполнении которых мы заинтересованы, чтобы было удобно работать:

В то же время они должны быть надежно сохранены и надежно воспроизведены. То есть, если мы что-то записали в базу данных, мы должны быть уверены, что мы это получим обратно.

Если вы работали со старыми базами данных, например, FoxPro, то знаете, что там часто появляются битые данные. В новых базах данных, типа MongoDB, Cassandra и прочих, такие проблемы тоже случаются. Может быть, просто их не всегда замечают, потому что данных уж очень много и заметить сложнее.

Для «железа» на самом деле важна надежность. Это как бы допущение, поскольку мы все-таки будем говорить о теоретических вещах. В нашей модели, если что-то попало на диск, то мы считаем, что там все хорошо. Как заменить вовремя диск в RAID — это сегодня для нас забота админов. Мы не будем глубоко погружаться в этот вопрос, и практически не будем касаться того, насколько эффективно хранилище организовано физически.

Чтобы решать эти проблемы, есть некоторые подходы, которые очень похожи у разных хранилищ данных — и новых, и классических.

Чем в базе данных представляется информация. Смотреть фото Чем в базе данных представляется информация. Смотреть картинку Чем в базе данных представляется информация. Картинка про Чем в базе данных представляется информация. Фото Чем в базе данных представляется информация

Прежде всего для того, чтобы обеспечить универсальный и оптимальный доступ к данным, есть язык запросов. В большинстве случаев это SQL (почему именно он, мы подискутируем дальше), но сейчас просто хочу обратить внимание на тенденцию. Сначала достаточно долгое время был SQL — конечно, были времена и до него, но, тем не менее, SQL господствовал долго. Потом стали появляться всякие Key-value-storage, которые, дескать, работают без SQL и гораздо лучше.

Многие Key-value-storage в основном делались для того, чтобы из любимого языка программирования было проще ходить за данными, а SQL не очень хорошо вяжется с любимым языком программирования. Он высокоуровневый, декларативный, а нам хочется объектов, поэтому появилась идея, что SQL не нужен.

Но большинство этих технологий сейчас на самом деле придумывают какой-то свой язык запросов. В Hibernate очень развит свой собственный язык запросов, кто-то использует Lua. Даже те, кто раньше использовал Lua, делают свои реализации SQL. То есть сейчас тенденция такая: SQL опять возвращается, потому что удобный человеко-читаемый язык работы со множествами все равно нужен.

Плюс к тому по-прежнему удобно табличное представление. В той или иной степени во многих базах данных по-прежнему имеются таблички, и это далеко не случайно — таким образом легче оптимизировать запросы. Вся математика оптимизации завязана вокруг реляционной алгебры, и когда есть SQL и таблички, работать гораздо проще.

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

Опять же, если что-то пошло не так и база данных упала, нам нужно ее быстро поднять.

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

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

Для «железа» на самом деле важно, чтобы база данных была хорошо интегрирована с ОС, работала производительно, вызывала правильные syscalls и поддерживала все фишки ядра по быстрой работе с данными.

Слой хранения

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

Слой хранения обеспечивает:

Параллелизм и эффективность.

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

Надежность: восстановление после сбоев.

Вторая проблема — это внезапный сбой. Когда обеспечивается надежность, это означает, что мы не только максимально обеспечили катастрофоустойчивое решение, но важно и то, что мы умеем быстро восстановиться в случае чего.

Конкурентный доступ

Когда я говорю о целостности, внешних ключах и прочем, все как-то хмыкают и говорят, что все это они проверяют на уровне кода. Но как только предложишь: «А давайте пример на вашей зарплате! Вам переводят зарплату, а она не пришла», — почему-то сразу понятней становится. Не знаю почему, но сразу появляется блеск в глазах и интерес к теме внешних ключей, констрейнтов.

Ниже код на несуществующем языке программирования.

Допустим, у нас есть банковский счет с балансом в 1 000 рублей, и есть 2 функции. Как они устроены внутри, нам сейчас не важно, эти функции переводят с аккаунта a на другие банковские счета 100 и 200 рублей.

Внимание, вопрос: сколько денег окажется в результате на балансе счета a? Скорее всего, вы ответите, что 700.

Проблемы

Здесь начинаются проблемы с конкурентным доступом к данным, потому что язык у меня выдуманный, совершенно не понятно, как он реализован, одновременно ли исполняются эти функции и как они устроены внутри.

Мы, наверное, считаем, что операция send_money() — это не элементарное действие. Надо проверить баланс и куда переводится, выполнить контроль 1 и 2. Это не элементарные операции, которые занимают какое-то время. поэтому нам важен порядок выполнения элементарных операций внутри них.

В последовательности «прочитали значение на балансе», «записали на другой баланс», важен вопрос — когда мы читали этот баланс? Если мы это делаем одновременно, возникнет конфликт. Обе функции выполняются примерно параллельно: прочитали одно и то же значение баланса, перевели деньги, записали каждая свое.

Может возникнуть целое семейство конфликтов, в результате которых на балансе может оказаться 800 рублей, 700 рублей, как должно быть, или что-то побьется, и на балансе окажется null. Такое, к сожалению, бывает, если не относиться к этому с должным вниманием. Как с этим бороться, мы и поговорим.

В теории все просто — мы можем выполнить их одну за другой и все будет хорошо. На практике этих операций может быть очень много и делать их строго последовательно может быть проблематично.

Если помните, несколько лет назад была история, когда у Сбербанка упал Oracle и процессинг карточек остановился. Они тогда просили совета у общественности и примерно обозначили, сколько логов писала база данных писала. Это огромные количества и конкурентные проблемы.

Выполнять операции строго последовательно не очень хорошая идея еще и по той простой причине, что операций много, а мы ничего не выиграем от параллелизма. Можно, конечно, разбивать операции по группам, которые не будут конфликтовать друг с другом. Такие подходы тоже есть, но они не очень классические для современных баз данных.

Чем в базе данных представляется информация. Смотреть фото Чем в базе данных представляется информация. Смотреть картинку Чем в базе данных представляется информация. Картинка про Чем в базе данных представляется информация. Фото Чем в базе данных представляется информация

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

Это живой пример возможной сериализации, когда общественность очень долго приучали к тому, что правила нужно соблюдать. Думаю, что все, кто ездит на машине по Москве понимают, насколько утопична эта картина.

В принципе, нам нужно то же самое сделать с данными, которые мы пишем на диск.

Как улучшить ситуацию?

● Операции должны быть независимы друг от друга — изолированость.

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

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

● Операция происходит по принципу «все или ничего» — атомарность.

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

● Нужен механизм как проверить что все произошло правильно — консистентность.

Я спрашивал, сколько денег получилось на балансе в нашем примере, и вы почему-то сказали, что 700. Мы все знаем, что есть арифметика, здравый смысл и Уголовный Кодекс, который следит за банками и бухгалтерами, чтобы они не сделали чего-то противозаконного. Уголовный Кодекс — это одна из частных версий консистентности. Если мы говорим, о базах данных, там их гораздо больше: внешние ключи, констрейнты, все дела.

ACID-транзакция

Действия с данными, которые обладают свойствами атомарности, консистентности, изолированности и Durability — это определение ACID транзакции.

D — Durability — это та самая модель, про которую я говорил: если данные уже записали на диск, то мы считаем, что они там лежат, записались надежно и никуда не денутся. На самом деле это не так, например, данные нужно бэкапить, но для нашей модели это не важно.

Как ни печально, обеспечить эти свойства можно только с помощью блокировок. Есть 3 основных подхода к шедулингу транзакций:

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

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

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

Как люди, работающие с базами данных, мы на самом деле всегда пессимисты. Мы ожидаем, что программисты напишут плохой код, поставщик поставит плохое железо, Марь Иванна выдернет из розетки сервер, когда будет мыть пол — чего только может не быть!

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

Нужны эффективные алгоритмы взятия и снятия блокировок, потому что, если просто блокировать все, что нам нужно, скорее всего, мы придем к очень тупой версии, когда мы выполняем все операции строго последовательно. Как мы уже знаем, это не эффективно с точки зрения утилизации параллелелизма, современных ЦПУ, количества серверов и т.д.

Семантика Эрбрана

Небольшое лирическое отступление, которое поможет понять, что будет происходить дальше. Жак Эрбран — французский математик первой половины XX века, который, кстати, изобрел рекурсию. Он придумал еще в докомпьютерные времена обозначать транзакции следующим способом:

Чем в базе данных представляется информация. Смотреть фото Чем в базе данных представляется информация. Смотреть картинку Чем в базе данных представляется информация. Картинка про Чем в базе данных представляется информация. Фото Чем в базе данных представляется информация

Здесь S — от слова schedule (расписание). Расписание транзакции включает в себя операцию — r (read — чтение) или w (write — запись). Еще бывает b (begin), с (commit) и т.п.

Что удобно — у нас есть 2 транзакции (цифры 1 и 2). Одна транзакция просто читает данные из какого-то ресурса (x), а вторая транзакция его тоже читает, делает какую-то математику на основе этих двух чтений x и записывает что-то в y.

Очень удобно — транзакции состоят из элементарных действий «чтение — запись, чтение — запись». Мы можем составить итоговое расписание, проверить его, есть ли в нем конфликты, с помощью всякой хитрой математики, и таким образом гарантировать, что все будет хорошо и целостно.

Для чего все это нужно?

Two Phase Locking

Один из основополагающих алгоритмов в современных базах данных — это так называемое двухфазное блокирование или 2PL (Two Phase Locking).

Двухфазное оно, потому что было подмечено, что для оптимизации взятия и снятия блокировок в базе данных удобно сделать это в 2 присеста:

Чем в базе данных представляется информация. Смотреть фото Чем в базе данных представляется информация. Смотреть картинку Чем в базе данных представляется информация. Картинка про Чем в базе данных представляется информация. Фото Чем в базе данных представляется информация

На рисунке 3 линейки обозначают транзакции и время их исполнения. Видно, что операция записи в первой транзакции ресурса x имеет ненулевое время, поскольку запись занимает какое-то время — пока диск повернется, пока странички туда уйдут и т.д.

Когда она начинается, в этой модели нет никаких других транзакций, поэтому запись началась и пишется. Но вторая транзакция тоже должна прочитать x. Эта транзакция не может взять блокировку на чтение x по той простой причине, что x в этот момент пишется другой транзакцией. Линия становится пунктирной — это означает, что транзакция ждет блокировки, которую выставила транзакция t1.

Как только транзакция t2 взяла все блокировки, которые ей были нужны для того, чтобы она выполнилась — ей еще нужна блокировка на y и блокировка на z — только тогда она может начать их отпускать. В этот момент разблокируется следующая транзакция, которая тоже выполняется до конца.

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

Рекомендую книгу «Transactional Information Systems» (Gerhard Weikum, Gottfried Vossen) — это фундаментальный учебник по теории транзакций.

Что плохо в двухфазном блокировании?

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

✓ Во-первых, с такими блокировками неизбежно возникают дедлоки, когда непонятно, курица или яйцо.

Одной транзакции нужен ресурс x, другой y, они его блокируют, а дальше им нужны крест-накрест те же самые ресурсы, и непонятно, кто должен первый отпускать блокировку. Для этого в базах данных имеются специальные системы контроля дедлоков и так называемого отстрела дедлоков. Дедлок нельзя разрешить мирным способом, а только откатом одной из транзакций.

Обычно математика внутри детекции дедлоков — это граф дедлоков, где на вершинах обозначены transaction ID, а направленные ребра обозначают, какая ждет блокировки от какой. На этом графе выделяются небольшие подграфы от одной из этих вершин, смотрится, например, если одну транзакцию ждет очень большое количество транзакций, то эту транзакцию прибивают.

Но есть и другие красивые математические подходы, искать которые можно по теме deadlock-detection.

✓ Второй момент, это медленно — никто не хочет ждать блокировки.

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

✓ Зато таким образом обеспечена сериализация.

Без двухфазного блокирования никакой сериализации нет. То есть надо придумывать, как улучшать двухфазное блокирование, чтобы сократить время на ожидание.

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

На самом деле бывают конфликты, которые 2PL разрулить не может в принципе и тогда одна из конфликтующих транзакций откатывается. Обычно в базах данных реализован механизм, когда база данных ждет некоторое время и понимает, что некоторая транзакция ждет блокировки слишком долго, и что нельзя никак разрешить конфликт, база данных просто убивает такую транзакцию. Это достаточно редкая ситуация и следующий алгоритм позволяет решить некоторые из этих конфликтов.

MVCC — MultiVersion Concurrency Control

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

✓ Интуитивно все понятно — чтобы не ждать блокировку, берем предыдущую версию.

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

В любом случае это обычно быстрее, чем долго ждать блокировку. Если помните старый MS SQL Server и старые версии DB2, страшное дело, то там, если там пошло много блокировок, дальше началась их эскалация — все работало плохо и жить с этим было тяжело.

✓ Все современные DBMS в той или иной степени «версионники»

Oracle, PostgreSQL, MySQL — все «версионники» в честном виде. DB2 немножко оригинальнее на эту тему, там есть свой механизм — хранят только одну предыдущую версию.

Чем в базе данных представляется информация. Смотреть фото Чем в базе данных представляется информация. Смотреть картинку Чем в базе данных представляется информация. Картинка про Чем в базе данных представляется информация. Фото Чем в базе данных представляется информация

Это расписание, которое я рисовал раньше, но несколько более сложное. Здесь больше транзакций (3 штуки), больше ресурсов (есть еще z) и 2 коммита. То есть обе транзакции заканчиваются коммитом.

Как говорят в таких случаях математики: «Легко заметить. » — я это очень люблю, особенно когда на половину доски формула. Действительно, тут легко заметить одну штуку. В качестве домашнего задания попробуйте понять, почему это легко заметить.

Я вам подскажу. Это расписание никогда не сериализуется по той простой причине, что операция r1(y) вызовет конфликт, возможно, даже дедлок, если не будет доступна предыдущая версия y.

То есть, если здесь будет доступна предыдущая версия y, то транзакция нормально завершится, никаких проблем не будет. Если этой версии y не будет, то операции будут конфликтовать.

Как это работает?

Чем в базе данных представляется информация. Смотреть фото Чем в базе данных представляется информация. Смотреть картинку Чем в базе данных представляется информация. Картинка про Чем в базе данных представляется информация. Фото Чем в базе данных представляется информация

Диаграмма более-менее такая же, как с двухфазным блокированием. Это разновидность версионного шедулинга транзакций, то есть это все равно алгоритм двухфазного блокирования, только мультиверсионный.

Добавляется еще такая фишка, как нижний индекс — 0, 1, 2 — это номер версии.

В этом большой плюс MVCC. Мультиверсионность на самом деле быстрее, чем блокировка, это не просто маркетинговая фича.

А что, если в момент транзакции, которая явно имеет ненулевую длительность, произойдет сбой, например, развалится жесткий диск под базой данных или выдернут провода из сервера?

На самом деле мы к этому готовы, потому что транзакции выполняются так. Рассмотрим абстрактную базу данных:

Чем в базе данных представляется информация. Смотреть фото Чем в базе данных представляется информация. Смотреть картинку Чем в базе данных представляется информация. Картинка про Чем в базе данных представляется информация. Фото Чем в базе данных представляется информация

Есть некий объем памяти, который обобществлен между разными процессами или тредами, в которых обрабатываются клиентские подключения. У треда есть свой объем памяти, куда приходит SQL-запрос. В этом объеме памяти SQL запрос (или запрос на другом языке) прекомпилируется, интерпретируется, перестраивается каким-то образом.

Дальше он идет за данными, которые ему нужно прочитать и изменить. Эти данные на диске лежат специальным образом. Если заглянуть глубже в хранение, они лежат фиксированными кусками (страничками) в PostgreSQL это 8Кб, в Oracle можно разного размера использовать. В разных базах данных по-разному.

Эта страничка очень удобна тем, что в ней лежит куча разных данных (фактически в ней лежат tuple (кортежи) То есть есть табличка, а в ней строчки, эти строчки упакованы в большие странички.

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

Если нужно поменять хотя бы одну запись хотя бы на одной страничке, вся страничка будет помечена, как так называемая «грязная». Это делается потому, что так удобнее. Мы рисовали на схеме ресурсы x и y — здесь это странички.

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

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

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

Поэтому прежде, чем ответ от транзакции вернется снова к клиенту, происходит запись в так называемые Write Ahead Log. Это та самая бумажка, которая позволяет записывать быстро — запись в WAL последовательная, нам не надо искать, куда вставить в огромный data-файл это дело.

Мы записали в лог информацию о страничке и дальше вернули управление — все хорошо. Если в какой-то момент упали, то читаем назад Write Ahead Log и используя информацию об этих изменениях, можем чистые странички докатить до уровня «грязных». База данных у нас снова новая.

Это позволяет произвести то самое восстановление, которое нам нужно было обеспечить, исходя из проблем хранения данных, и позволяет восстановиться на самую последнюю транзакцию, на самое последнее действие, которое произошло перед тем, как Марь Иванна вытащила сервер из розетки.

Этот алгоритм называется ARIES и в современном виде сделан достаточно давно. Фундаментальная статья по его устройству и способе восстановления в реляционных базах данных была опубликована Моханом в 1992 году.

С тех пор теории особо не добавилось — Write Ahead Log с тех пор остался Write Ahead Log. Они все используют концепцию страничек и концепцию записей изменений в лог. Лог может по-разному называться и в разных местах располагаться:

Важный момент состоит в том, что все это было бы очень непроизводительно, если бы мы просто от начала времен писали WAL. Он бы рос и рос, а мы потом очень бы долго накатывали эти изменения в базу данных.

Checkpoint

Поэтому в последней реинкарнации этого алгоритма имеются идея так называемых Checkpoint — периодически база данных выполняет синхронизацию «грязных» страниц на диск. Когда мы будем восстанавливаться, можно просто дойти только до предыдущего Checkpoint. Все остальное уже синхронизировано, мы, так сказать, замечаем до какого момента мы восстановили.

Это как в компьютерной игре — люди, когда ее проходят, сохраняются периодически. Или как в Word сохраняют периодически свои файлы, чтобы текст не пропал никуда.

База данных это все умеет делать внутри себя. Она так устроена. Помимо всего прочего, это немножко ускоряет процесс, потому что рано или поздно эти странички должны же попасть на диск.

Доступ к данным

Эти самые странички, конечно, хорошие, но нам нужно в каком-то более человеко-читаемом виде эти данные получить. Страничная модель удобна для хранения и транзакционных алгоритмов, но не удобна для доступа. Странички читать неудобно — по битам, поэтому нужен более удобный человеку эффективный метод доступа к данным в страницах.

Чем в базе данных представляется информация. Смотреть фото Чем в базе данных представляется информация. Смотреть картинку Чем в базе данных представляется информация. Картинка про Чем в базе данных представляется информация. Фото Чем в базе данных представляется информация

Есть странички на диске, относящиеся к таблице A и B. Данные на диске ничего не знают, к какой табличке они относятся. Про это знает наш оптимизатор, то есть тот engine в базе данных, который исполняет наш запрос, написанный на нашем языке запросов.

Например, если рассмотреть традиционный SQL, то обычно такая штука будет называться планом запроса. С помощью последовательного sequential scan мы будем брать странички из таблицы A и из таблицы B — иногда синхронно, иногда по очереди — в зависимости от реализации.

Дальше будем накладывать на них, например, JOIN, а с результатом делать что-нибудь еще, и потом вернем ответ клиенту.

Чем это удобно? Представьте себе альтернативу: вы из какого-нибудь Python читаете все это к себе в приложение, а эти таблички могут быть на самом деле огромными, а условие JOIN может исключать 90% этих данных. Вытаскиваете в память — там соответственно ходите по ним циклами, сортируете, возвращаете. На самом деле на каждом из этих этапов планировщик может решить, как сделать выгоднее. Например, он может выбрать метод JOIN, который может или целиком состоять из циклов, или может закэшировать одну таблицу и присоединить к ней другую и т.д. В зависимости от метода, например, можно не делать full sequential scan, то есть не читать всю табличку, а из приложения, скорее всего, вам придется прочитать данные целиком.

Здесь все придумано до вас и реализовано эффективно.

Выводы

Хранить данные удобней страницами. Есть разные базы данных: объектные, графовые, документо-ориентированные. В основном это все нишевые, неуниверсальные продукты, которые используются для своих целей. Просто поставить на такую базу огромный объем транзакций, чтобы они работали в универсальном виде, не особо получается. Шедулинг транзакций, которые мы рассматривали здесь, на объектах выглядит гораздо сложнее.

Фактически каждая транзакция представляет собой путь по графу. Граф может быть очень большим. Для нахождения конфликтов нужно заниматься очень тяжелой математикой на графах — поэтому в объектной модели возникают проблемы с шедулингом.

Неслучайно столько лет господствует база данных со страничной моделью, и поэтому все происходит так, как происходит. Хорошо ли, плохо ли, но этот метод доказал свою большую эффективность.

Транзакции, вопреки расхожему мнению, это не способ замедлить. Часто считают, что транзакции — это некий синтаксический сахар в базах данных с SQL, который просто все портит и бибикает, в смысле того, что все замедляет, потому что ждем блокировок.

На самом деле транзакции — это способ ускорения, поскольку позволяет параллельно обрабатывать больше данных без конфликтов, чтобы они не бились, чтобы получалось так, что операции выполнялись не строго одна за другой, а параллельно. Это позволяет эффективней расходовать вычислительные ресурсы и время.

Как говорил конструктор Туполев, когда его обвиняли в том, что он какую-нибудь модель самолета у кого-нибудь стянул: «Все самолеты одинаково устроены — чтобы летать, им нужно иметь крылья, фюзеляж и хвост!»

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

Если посмотреть на современные Percona server, MariaDB, MySQL 8, видно, что они в значительной степени перенимают теоретические основы и сейчас гораздо больше похожи на классические базы данных по своему устройству.

Ну что, убедились, что общие теоретические аспекты нужно знать?

Но не теорией единой… и на РИТ++, который успешно завершился, и на Highload++ Siberia уже через месяц, всегда много-много реального опыта и практических кейсов.

Например, про базы данных и системы хранения есть такие потрясающие заявки:

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *