Структура баз данных — Основы SQL
Почти все приложения работают с большим количеством информации, которая хранится в виде таблиц в базах данных.
В этом уроке мы более подробно поговорим о структурах и выясним, как устроены таблицы и базы данных.
Что такое таблица
Рассмотрим для примера Хекслет. Мы храним информацию о студентах и о курсах. При этом нужно разнести информацию по двум разным таблицам, чтобы было удобно работать. Другими словами, в разных таблицах мы храним разные сущности.
В базе данных Хекслета сущность «Студенты» будет храниться в одной таблице, сущность «Курсы» — в другой.
Сама таблица представляет собой набор столбцов и строк. Пересечение столбца и строки — это ячейка.
В таблице «Студенты» каждая строка содержит в себе информацию об одном конкретном человеке. Еще строки называют записями.
Каждый столбец определяет, какого типа данные хранятся в ячейке, например:
Еще столбцы иногда называют полями.
Таблица с данными студентов Хекслета может выглядеть так:
Таблицы в базах данных
Многие привычные приложения содержат десятки, сотни и тысячи таблиц с разнообразными данными.
Чтобы всем было удобно работать с такими объемами данных, к таблицам предъявляются конкретные требования:
Название таблицы. Оно должно быть уникально в рамках одной базы данных. Название таблицы мы задаем при создании, но его можно изменить при необходимости
Столбцы или поля. У каждого поля уникальное имя в рамках одной таблицы
Тип данных столбца. Он ограничивает набор допустимых значений, которые можно присвоить столбцу. Например, в столбец числового типа нельзя записать текстовые строки. Тип данных присваивается каждому столбцу
Строки или записи. Их количество в разных таблицах сильно отличается — от нескольких штук до миллиардов записей. В базах данных нет никаких гарантий относительно порядка строк в таблице
Базы данных, в которых данные хранятся в виде таких таблиц, называются реляционными базами данных. Работать с ними можно с помощью реляционных СУБД.
Структура таблиц
Таким образом, каждая таблица должна иметь определенную структуру.
Эта структура определяет столбцы и включает в себя имена полей и их типы:
Рассмотрим этот пример подробнее. В нем прописаны названия полей и типы:
Поля first_name , last_name и email содержат обычный текст, поэтому мы задаем тип данных string (строки)
В поле created_at мы сохраняем дату добавления пользователя в систему. Для этого поля установлен тип данных datetime , поэтому туда нельзя записать текст
Как хранятся данные в sql
В SQL Server выделяют отдельные типы страниц GAM-page и SGAM-page. Каждый бит такой страницы кодирует информацию о том выделено ли место под экстент (GAM) и если да, однородный ли он (SGAM). Каждая такая страница содержит информацию о 64000 экстентах, что примерно 4Гб.
- Насколько заполнена страница с данными, возможные значения
- 0_PCT_FULL — пустая
- 50_PCT_FULL — 51%-80%
- 80_PCT_FULL — 81%-95%
- 100_PCT_FULL — 96%-100%
1. DBCC IND
3. Функция sys.fn_PhysLocFormatter
Структура страницы
Все дисковые операции производятся непосредственно на уровне страниц. Страница содержит заголовок размером 96 Байт, который хранит её метаданные, раздел с данными и таблицу отступов (slot array, каждая ячейка использует 2 байта — положение первого байта строки с данными). Все страницы нумеруются последовательно. Данные на страницу записываются сверху вниз, а таблица отступов формируется снизу вверх. При этом логический порядок строк поддерживается именно на уровне таблицы отступов, на самой странице данные могут храниться в неупорядоченном виде.
- 2 байта со статусами
- 2 байта на общий размер полей с фиксированной длиной (отступ на позицию, где заканчиваются данные)
- N байт данных фиксированной длины
- 2 байта на количество колонок в строке
- (Количество колонок / 8) байт — карта полей, содержащих нулевые значения для текущей строки
- 2 байта на количество полей с переменной длиной
- (2 * Количество полей переменной длины) байт — таблица отступов для полей переменной длины
- M байт данных переменной длины
- 0 — заголовок страницы
- 1 — заголовок, дамп каждой записи и таблица смещения
- 2 — заголовок и дамп всей страницы
- 3 — заголовок и расширенная информация по каждой записи
Также выполним команду с параметром 1, чтобы просмотреть данные таблицы смещения
Проанализируем полученные данные.
Для начала вычислим размер одной записи (ориентируясь на данные, которые мы вставили в таблицу) внутри IN_ROW страницы по формуле описанной выше.Row size = 6 + 4 + 2 + 6 + 1 + 50 + 24 + 36 = 129
Где 4 байта на колонку типа int
50 байт на строку varchar(50). заполненную полностью;
24 байта на указатель на данные колонки varchar(8000), размещенной в ROW_OVERFLOW странице
36 байтов на указатель на LOB данные, для строки типа varchar(max). 12 байт на метаданные и по 12 байт на каждую LOB страницу. В нашем случае их 2
Итого, получаем 129 байт на хранение одной строки на IN_ROW странице.
Наши вычисления подтверждаются при просмотре данных страницы, так как она содержит поле Record Size = 129.Перевод: Как MS SQL Server выполняет запросы. Часть 2
Продолжаю публиковать перевод статьи Remus Rusanu (CC-BY), о том, как MS SQL Server выполняет запросы. В этой части разберём, как данные хранятся внутри БД, а также как именно происходит их считывание в рамках запроса.
Организация данных
Поговорим о том, как SQL Server хранит данные, потому что без этого будет сложно разобраться, как SQL Server обращается к ним при выполнении запроса. Есть три основных варианта хранения данных в SQL Server:
Куча. Это неупорядоченная таблица. Куча хранит данные всех колонок таблицы. Если таблица организована как куча, то при любом упоминании «кучи» подразумевается сама таблица – и наоборот.
Если при создании таблицы вы не указали Primary key – это куча. Если выполнили Select … INTO – то в результате тоже получите кучу. Подробнее о внутренней организации куч рассказано в документации.
Кластерные индексы. В отличие от кучи, это упорядоченная таблица. Кластерный индекс хранит в себе все колонки таблицы. Если таблица организована как кластерный индекс, то такой индекс и есть таблица. Кластерный индекс представляет собой сбалансированное дерево (B-Tree)
Если вы объявили первичный ключ (Primary key) на таблице, этот самый ключ будет использован для кластерного индекса (то есть, индекс будет построен по колонке PK). Если, конечно, вы не стали явно объявлять PK как NONCLUSTERED.
Подробнее о внутренней организации кластерного индекса также рассказано в документации.
Некластерные индексы. Это копия части таблицы, упорядоченная определенным образом. Некластерный индекс содержит в себе одну или несколько колонок из таблицы. Когда речь заходит об «индексах», обычно имеют в виду как раз некластерные индексы.
Нексластерные индексы, так же, как и кластерные — являются сбалансированными деревьями. Детальнее структура хранения некластерных индексов описана в BOL.
От переводчика. По моему скромному мнению, это самая неудачная часть статьи – всё объяснено, но понятнее от этого не становится. Поэтому позволю себе вмешаться.
И кластерные индексы, и некластерные представляют собой деревья. Но у кластерного индекса на последнем, листовом уровне, содержатся сами данные. А некластерный вместо этого хранит ссылки на основное хранилище данных – кластерный индекс или, если его нет, на записи кучи.
Начиная с SQL Server 2012 появились новые способы организации данных – некластерные колоночные индексы (nonclustered columnstore index), а ещё позже – и кластерные колоночные индексы (clustered columnstore index). Об особенностях их работы можно узнать в статьях по соответствующим ссылкам.
Доступ к данным
На «листьях» дерева выполнения (помним про то, как выглядит план запроса?) находятся операторы физического доступа к данным. Эти операторы возвращают строку данных из таблицы (или индекса) каждый раз, когда вызывается их метод next(). Есть три метода доступа к данным:
Оператор сканирования (Scan)
Оператор сканирования пробегается по всем строкам источника. Этот оператор не может выбрать одну конкретную строку – ему всегда приходится сканировать весь набор данных.
Если вы посмотрите на содержимое плана выполнения, вы скорее всего встретите один из этих операторов: Clustered Index Scan, Nonclustered Index Scan, Table Scan, Remote Index Scan или Remote Scan. Это действительно разные операторы, т.к. они обращаются к разным источникам (соответственно, отличается внутренняя механика работы). Но они едины в одном – каждому из этих операторов во время выполнения приходится выполнять полное сканирование источника, от начала до конца.
Поскольку таким операторам всегда приходится считывать весь источник данных, они оказываются довольно дорогими (как минимум, с точки зрения ввода-вывода). Обычно использование таких операторов – это удел тяжёлых аналитических запросов.
Оператор поиска (Seek)
Этот оператор может найти нужную строку при помощи ключа индекса. Поиск выполняется только в структурах сбалансированных деревьев – поэтому, он может работать только по кластерным и некластерным индексам.
Если индекс построен по нескольким колонкам, оператор поиска может сработать только если у него есть значения для самых левых (или первых) колонок в индексе. Разберём это на примере: предположим, у нас есть индекс, построенный по трём колонкам (A, B, C). В этом случае поиск может найти строки, если в условии поиска указаны значения для колонки A (A = ‘a’), для колонок A и B (A = ‘a’ AND B = ‘b123’) или для всех трёх колонок (A = ‘a’ AND B = ‘b123’ AND C = ‘c15’). Но если мы пропустим первую колонку (B = ‘b123’ AND C = ‘c15’) оператор поиска уже будет бессилен найти что-либо.
Кроме чтения отдельных строк, оператор поиска может выполнять поиск диапазонов. Если продолжить наш пример, оператор может найти все строки, где A находится между ‘a’ и ‘z’. Или строки, где A=’d’ и B между ‘b’ и ‘n’. Но не сможет найти строки с условием только по B: B > ‘b’ AND B < ‘n’.
Если вы посмотрите на план выполнения, вы можете встретить операторы Clustered Index Seek или Remote Index Seek. Они обращаются к разным источникам, но каждый из них может быстро найти строку по ключам индекса или же перебирать строки в отведённом диапазоне. Очевидно, не существует оператора для поиска в куче – просто потому, что куча это по определению неупорядоченная структура, для выполнения поиска ее всегда приходится прочитывать полностью.
В большинстве ситуаций оператор поиска является наиболее предпочитаемым методом доступа к данным (я бы не говорил так громко, но это точно справедливо для быстрых OLTP-запросов – прим. перев.)
Оператор поиска по закладкам. (Bookmark lookup)
Это особенный оператор доступа, который может быстро найти строку при помощи специального типа значений – закладки (bookmark). Мы не можем самостоятельно определить закладку – движок СУБД делает это самостоятельно. И в этом состоит особенность этого оператора – он не может быть «первичным», «исходным» оператором чтения данных. Кто-то должен предоставить ему значения закладок, поэтому этот оператор всегда обращается к ключам, которые до этого уже были прочитаны либо оператором сканирования, либо оператором поиска.
Этот оператор может применяться как вместе со структурированными источниками (индексы), так и с кучей. В плане выполнения он может отображаться как Bookmark Lookup, Row ID Lookup (поиск по куче) или Key Value Lookup (поиск, специфичный для B-деревьев).
Подытожив:
Сканирование (Scan) считывает все данные
Поиск (Seek) считывает только минимально необходимый набор данных
Кучи (heap) поддерживают только сканирование
Сбалансированные деревья (т.е. индексы) могут выполнять поиск только если установлен отбор по колонкам, находящимся в начале ключа индекса (т.е. по полям, находящимся «с левого края» списка полей индекса)
Строго говоря, операторы, применяемые для вставки, удаления или модификации строк – это тоже операторы доступа к данным. Например, Inserted Scan и Deleted Scan – это операторы для доступа к псевдо-таблицам inserted и deleted, которые появляются в контексте выполнения триггера. Или вот оператор Log Row Scan – ещё более эзотерический способ доступа к данным, который обращается не к таблицам с данными, а к логу транзакций. Но если мы продолжим спускаться в такие детали, мы слишком далеко отклонимся от цели этой статьи.
Вы можете часто встречать упоминание концепции «сканирование диапазона» (Range Scan). В этом случае имеют в виду оператор поиска (Seek), который быстро находит одну строку по ключу, а затем перебирает последующие строки – иногда, до нахождения другого определённого значения ключа. Получается, что оператор поиска выполняет сканирование внутри диапазона, определённого начальным и конечным значениями ключей. Именно поэтому и используют термин «сканирование диапазона».
Если мы вспомним, как именно происходит выполнение запроса, мы поймём, что именно операторы доступа к данным приводят в действие цикл выполнения запроса. Когда метод next() вызывается для самого верхнего оператора, он передаёт вызов дальше по дереву выполнения, пока не доберётся до оператора доступа к данным. Этот оператор при реализации next() как раз считывает очередную строку данных из соответствующего источника (кучи или B-дерева) – и возвращает считанную строку вверх по дереву. Оператор помнит, где остановилось чтение и при следующем обращении прочитает следующую строку. Операторы доступа к данным не могут иметь подчинённых операторов, они всегда находятся на листовом уровне дерева выполнения. С другой стороны, операторы, располагающиеся выше по дереву, реализуют разные манипуляции со считанными данными: соединение, отбор, сортировка результатов, вычисление агрегатов и т.д.
Чтение данных
Операторы доступа к данным всегда считывают данные из кэша и никогда – с диска. Этот кэш называется Buffer Pool. Если данных нет в кэше, оператор должен запросить их с диска (провоцируя обращения ввода-вывода к диску) и ждать, пока они не будут считаны с диска. Данные в кэше (Buffer pool) доступны всем выполняемым запросам – таким образом, после того, как данные были прочитаны и попали в кэш, любой следующий запрос уже воспользуется преимуществами кэша и не будет тратить время на обращение к диску.
SQL Server всегда читает в кэш столько данных, сколько может. При этом увеличивается частный объем памяти процесса (process allocated private memory), пока вся память системы не достанется SQL Server. Именно поэтому администраторы всегда настраивают максимальный порог потребления памяти (max server memory).
Buffer Pool, так же, как и отдельные операции чтения/записи, никогда не работает с отдельными строками. Вместо этого он работает со страницами, размер которых всегда равен 8Кб.
Посмотрим, как оператор доступа данных (в данном случае Сканирование) будет читать данные из кучи:
При первом вызове next() оператор должен найти первую подходящую строку и вернуть ее. SQL Server хранит метаданные о том, какие именно страницы относятся к таблице. Те, кому интересны подробности, могут обратиться к статье Managing Space Used by Objects и Inside the Storage Engine: IAM pages, IAM chains, and allocation units. Так вот, оператор доступа к данным обращается к Buffer Pool чтобы получить указатель на нужную страницу (точнее, на её расположение в памяти). Если страница не найдена в памяти, запрос приостанавливается до тех пор, пока страница не будет считана с диска.
Страница содержит массив из отдельных записей. Запись не обязательно соответствует целой строке данных. Дело в том, что большие значения или поля переменной длины могут храниться на другой странице. Рекомендую прочитать статью Inside the Storage Engine: Anatomy of a record, где раскрыты дополнительные подробности того, как строки располагаются внутри страниц.
Оператор доступа к данным находит первую строку на странице, считыавет оттуда нужные значения полей и возвращает их вышестоящему оператору. При этом, оператор доступа сохраняет своё внутреннее состояние – это позволяет ему мгновенно вернуться к нужной позиции (к той же странице и строке).
Родительский оператор получает первую строку, переданную оператором доступа к данным. Для получения следующей строки ему нужно будет опять вызвать next() у нижестоящего оператора.
Когда метод next() будет вызван у оператора доступа в следующий раз, этот оператор воспользуется своей памятью состояния: быстро спозиционируется на последней считанной строке, переместится на следующую строку, прочитает нужные данные и передаст управление обратно вышестоящему оператору.
Когда при очередном вызове оператора доступа к данным все строки из текущей страницы будут считаны, оператор запросит у Buffer pool ссылку на следующую страницу. Какая именно страница будет «следующей» описано в метаданных таблицы – тут я опять отсылаю к статье Пола Рэндалла про IAM-страницы, которая подробно раскрывает эту тему.
Как только следующая страница окажется в Buffer Pool, оператор прочитает первую запись (то есть, скопирует нужные поля из этой записи) и вернёт управление назад, к вышестоящему оператору.
Всё это продолжается, пока не будет прочитана последняя строка, принадлежащая таблице. В этот момент внутренний указатель оператора доступа переместится в состояние «за границами таблицы» и оператор не сможет больше вернуть ни одной строки.
Когда оператор доступа к данным больше не может возвращать строки, вышестоящий оператор переходит к следующей стадии: выполняет то, что нужно сделать, когда нижестоящий оператор «завершён».
Например, SORT в этот момент может начать возвращать строки для своего вышестоящего оператора. HASH JOIN начинает перебирать «проверяемую» сторону – и возвращать найденные строки. А SELECT в этот момент возвращает False, что в конце концов приводит к завершению выполнения запроса.
Оператор доступа к данным можно «перемотать назад». Например, если он оказался во «внутренней» части оператора Nested Loops (вложенные циклы), то после завершения сканирования для текущей строки, внешний «ввод» Nested Loops передаст следующую строку – и внутреннему оператору придётся повторить поиск для этой новой строки. В этом случае указатель внутреннего состояния сбрасывается – и полный цикл сканирования повторяется заново, начиная с первой строки первой страницы.
Для сравнения, вот как оператор доступа будет работать с отсортированным B-деревом (помним про индексы?):
При первом вызове next() оператор должен найти (Seek) первую строку, которая содержит искомый ключ поиска. В метаданных, которые SQL Server сохраняет для B-деревьев, есть информация о том, какие страницы принадлежат индексу. Напомню, что в случае с кучей (heap) нам надо было начинать поиск с самой первой страницы. В отличие от кучи, здесь оператор может сразу спозиционироваться на строке, содержащей искомый ключ.
Оператор получает из метаданных идентификатор корневой страницы, затем обращается за ссылкой на неё к Buffer Pool. Используя ключ поиска оператор постепенно «спускается» по дереву вплоть до листовой страницы, содержащей строку с ключом, равным ключу поиска, либо следующим сразу следом за ним. Спускаясь по дереву, на каждом шаге оператор доступа должен запросить из Buffer Pool соответствующую страницу – и, возможно, дождаться, пока она будет считана с диска.
Добравшись до листового уровня, оператор доступа должен будет просмотреть строки, содержащиеся на странице, чтобы найти строку с нужным ключом – затем скопировать нужные значения колонок и вернуть управление вверх по дереву плана.
Допустимо, что оператор поиска вообще не найдёт строки, удовлетворяющей условиям поиска.
Оператор доступа может искать строку по полному совпадению ключа поиска (т.е. «равно»); может искать строку, следующую за ключом поиска (т.е. «больше») или строку, равную или следующую за ключом поиска (т.е. «больше или равно») . Поиск по сбалансированному дереву может выполняться в любом направлении: как «по возрастанию», так и «по убыванию». Поэтому определение строки, следующей за ключом поиска, зависит от направления поиска. Это направление не стоит путать с указанием порядка сортировки для ключа при создании индекса: в последнем случае речь будет идти как раз о физическом порядке следования строк.
Продолжаем. Вышестоящий оператор получает первую строку, переданную оператором Seek
Если оператор доступа используется в рамках «сканирования диапазона», для него будет снова выполнен метод next()
Оператор доступа к сбалансированному дереву помнит последний ключ, который он вернул. При повторном обращении он использует этот ключ, чтобы спозиционироваться на последней считанной строке, используя алгоритм, разобранный выше. После того, как эта последняя строка найдена, оператор просто переходит к следующей строке на странице и возвращает её.
Если на текущей странице закончились строки, оператор обращается к Buffer pool за следующей страницей – и вернёт первую строку с этой страницы (и, естественно, если нужной страницы в Buffer pool не окажется, придётся подождать, пока она будет считана с диска). Тут важно заметить, что листовые страницы B-дерева cвязаны между собой: страница знает номера своих соседей слева и справа. Так что для поиска нужной страницы не придётся повторять весь маршрут по дереву индекса; СУБД сразу знает, какую страницу прочитать.
Вышестоящий оператор получает строку, которую вернул оператор доступа к данным
У «Сканирования диапазона» может быть задано значение границы диапазона. В таком случае, если значение в очередной считываемой строке окажется больше, чем значение границы диапазона, весь вызов next() вернёт False (то есть, не будет возвращать ни одной строки).
Понятие «больше границы диапазона», конечно же, относительно, поскольку поиск по индексу может выполняться как «вверх», так и «вниз», да и сам индекс может быть отсортирован как по возрастанию, так и по убыванию.
Получается, оператор может завершить сканирование по двум причинам: либо найдёт строку за пределами границы сканирования, либо достигнет последней строки последней страницы дерева.
И последнее: операторы доступа к деревьям могут быть не только «перемотаны» (rewind), но и «связаны заново» (rebind). Перемотка сбрасывает оператор к начальной точке, но сохраняет те же значения отбора (по ключу или по диапазону). Повторное связывание, кроме этого, изменит и сами параметры отбора.
Упреждающее чтение (Read ahead)
Из описания работы оператора сканирования вы можете заметить, что каждый раз, когда он заканчивает чтение строк со страницы, ему тут же требуется получить из Buffer pool следующую страницу. Если нужной страницы в Buffer pool нет, приходится ждать, пока она не будет считана с диска. И если ожидания чтения с диска будут повторяться после каждой обработанной страницы, общая производительность запроса просядет.
Решение – заранее считать в память те страницы, которые пока ещё не были запрошены, но которые скоро понадобятся оператору доступа для выполнения запроса. SQL Server именно так и поступает: выполняет асинхронное упреждающее чтение тех страниц, которые будут запрошены оператором, ещё до того момента, как оператор запросит их для обработки строк. Таким образом, к тому моменту, как оператор обратится к странице, она уже будет находиться в Buffer pool – и доступ к такой странице будем практически мгновенным. Подробности этого механизма описаны в статьях Reading Pages и Sequential Read Ahead. Кроме того, существует отдельный вариант упреждающего чтения для доступа к случайным страницам в рамках вложенных циклов (Nested Loops): Random Prefetching.
в следующей части — обзор механизма блокировок и разбор процесса записи изменений в БД
“Техническая база” для менеджеров продукта — часть 4— базы данных

Записи из курса по technical product manager; позиционируют как: “Говори с разработчиками на одном языка”.
Это 4я часть из серии, в ней собраны: классификация баз данных, выбор БД под проект, ORM, типы связей, sql и non sql, кэширование.
Базы данных: хранение и обработка
Для реализации БД системы (структура) нет готовых решений. Это делает разработчик и бизнес — аналитик на этапе описания ТЗ.
SQL — язык структурированных запросов;
Все базы данных делятся на 2 типа: реляционные (SQL) и не реляционные (nonSQL).
Выбор БД, так же как и языка, зависит от задачи и типа данных. Стоит для себя ответить (составить запрос, провести анализ) на вопросы:
- простые: не содержат связей, несут описательный характер, простая связь key — value;
Например: стол <высота, ширина, цвет, кол-во ножек, цена>; - сложные: имеют связи, зависимости,
например: user, данные от системы <>, возраст>; - запись: большое кол-во операция записи; key-value или колоночная;
- чтение: много выборок данных, поиск связей;
- отображение: для отчетов;
- аналитика: для поиска инсайтов;
- умеренноданных:
- многоданных: (что такое много никто не знает), принято считать, когда больше одного носителя (сервера), кластера;
ORM (Object-Relational Mapping) — технология программирования (библиотека), которая связывает базу данных с концепциями языков программирования:
- объектное представление данных (модель): работа с сущностями кода, а не объектами БД;
- легко покрыть тестами;
- легко поддерживать и модифицировать;
- проектируем модели на уровне бизнес логики: изменения через код, влияют на хранение.
Проектирование модели: описание (на уровне кода), как будут выглядеть данные, с которыми программа будет взаимодействовать; нам не нужно знать, как это будет храниться в БД; есть служебные модели — знают всю информацию; модели для API — ограниченные модели для сторонних сервисов (без некоторых полей данных, урезанная модель). В базе — мы работаем с цифрами/значениями; в коде — с объектами/структурами;
Рекомендация: стоит использовать во всех проектах ORM (вместо чистого sql), т.к. он дает больше гибкости, легче настроить, меньше рутины, и тд.
Реляционные (SQL) БД:
Все данные хранятся в строго описанных таблицах (название колонок, тип данных, объем) и содержат связи между колонками по ключам и значениям (индексы). У них низкий порог входа. Такие БД похожи на Excell таблицы. Используют единый язык запросов — SQL.
Индексы (гуманитарное объяснение): способ сортировки данных в ножном порядке (удобном для поиска).
Например: есть телефонный справочник, где все абоненты записаны по фамилиям и по алфавиту;
если нужно найти Яковлева, то нет смылса листать весь справочник, можно сразу пойти с конца;
но, если нужно найти человека по имени (не зная фамилию)?
нужен второй справочник, который отсортирован по имени.- 1 — к — 1: одной записи в табилце А, отвечает одна запись из таблицы Б; свзяь по полю id (индекс); работает в обе стороны;
например: когда нужно вынести телефоны абоненентов или адрес в отдельную таблицу, чтобы не было избиточности; - 1 — к — многим: одной записи в табилце А, отвечают несколько записей из таблицы Б;
например: один абонент, может иметь несколько телефонов или адресов; - много — к — многим:
например: есть таблица с ролями (админ, польтзователь, бан), много пользователй в каждой группе; при этом в разном порядке;
Почему нельзя хранить все данные в одной таблице?
Избыточность: дублирование или хранение в одной таблице БД информации, которая там не должна быть (или уже есть в дургой таблице, или можно рассчитать по доступным данным, или сенсетив данные);
Их нужно нормализовать — привести в порядок;Как выбирать данные?
Для этого нужно сделать запрос на языке SQL. Его нужно выучить отдельно. Пример запроса:select “что нужно выбрать”
from “названеи таблицы”
where “фильтры”Не реляционные (nonSQL) БД:
Такие БД не имеют связей (таблиц, отношений, id), данные хранятся в другом виде (без строгой структуры), не имеют единого языка запросов, имеют большую скорость обработки данных, легко масштабируются, нет транзакций (запросы потоковые, если хотя бы один из запросов не прошёл, запрос отменяется).
Способы организации не реляционных БД:
- Табличная: ключ — значения, плоская таблица, где соотносятся свойства;
например: хранение в браузере — local store; session store; reddis — хранение данных в операционной памяти для быстрой обработки;
базы: Riak, Amazon DynamoDB. : каждая колонка — это как бы отдельная таблица из одной колонки, которая хранит только свои значения и id для связи; все данные хранятся в отсортированном виде; структура хранения данных напоминает индексы в обычных базах данных, однако между колонками нет физической связи;
например: событийные таблицы, над которыми выполняются сложные выборки (агрегации, фильтры, сортировки, аналитических выборок) типа добавление товаров в корзину в интернет магазине;
базы: Vertica, HBase, Cassandra. - Документоориентированные: в них нет понятия таблицы, но есть индекс (со своей структурой); очень похожа на табличную (ключ — значение), но значение представляет собой не строку, а объект (json, xml: несколько полей внутри);
базы: Couchbase и MongoDB. : используют рёбра и узлы для представления данных; узлы связаны между собой определённым отношениями, представленными рёбрами между ними; у узлов и отношений есть некоторые свойства. Используються в обучении нейронних сетей, геймдеве (сообщечства), соц сетях, контекстной релкаме.
базы: GraphQL.
Кэширование
Часто, встает задача оптимизировать запросы: на выборку данных, при ответе API и тд. Для этого, можно кэшировать на время ответы.
Клиент делает запрос: через сайт из браузера, запрос летит на веб-сервер, тот обрабатывает его, обращается к коду, обращается к БД за данными, формирует и отдает ответ.