Почему временные таблицы регистрируют время начала транзакции?

8

При обновлении строки во временной таблице старые значения строки сохраняются в таблице истории с временем начала транзакции в качестве SysEndTime. Новые значения в текущей таблице будут иметь время начала транзакции как SysStartTime.

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

BOL говорит:

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

Пример: я начинаю обновлять все строки в моей таблице «Заказы», 20160707 11:00:00и транзакция занимает 5 минут. Это создает строку в таблице истории для каждой строки с SysEndTimeas 20160707 11:00:00. Все строки в текущей таблице будут иметь SysStartTimeоф 20160707 11:00:00.

Если бы кто-то выполнил запрос в 20160707 11:01:00(пока запущено обновление), он бы увидел старые значения (при условии, что уровень изоляции зафиксирован для чтения по умолчанию).

Но если бы кто-то затем использовал AS OFсинтаксис для запроса временной таблицы, как это было, он увидел 20160707 11:01:00бы новые значения, потому что они SysStartTimeбыли бы 20160707 11:00:00.

Для меня это означает, что он не показывает эти строки, как они были в то время. Если бы он использовал время окончания транзакции, проблема бы не существовала.

Вопросы: это дизайн? Я что-то пропустил?

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

Это должно позволить вам воссоздать проблему.

Джеймс Андерсон
источник
1
Вы ответили на свой вопрос: если вы используете время окончания транзакции, у вас есть другое обновление в конце транзакции: обновление завершается, 20160707 11:04:58и теперь вы обновляете все строки с этой отметкой времени. Но это обновление также выполняется в течение нескольких секунд и заканчивается на 20160707 11:05:02какой временной отметке является правильное завершение транзакции? Или предположим, что вы использовали Read Uncommitedв 20160707 11:05:00, и получили строки, возвращенные, но позже AS OFне показывает их.
dnoeth
@dnoeth Да, я думаю, что этот «вопрос» является скорее разъяснением моей теории.
Джеймс Андерсон
Я не углублялся в реализацию SQL Server, но у Teradata были двухвременные таблицы в течение многих лет, и я всегда рекомендую прочитать этот пример из Ричарда Снодграсса (парня, который «изобрел» временные запросы), он основан на синтаксисе Teradata до ANSI SQL , но понятия те же: cs.ulb.ac.be/public/_media/teaching/infoh415/…
dnoeth

Ответы:

4

Идея состоит в том, чтобы отслеживать логическое время против физического времени. Логический просто относится к тому, что пользователь / приложение ожидает время вставки / обновления / удаления. Тот факт, что операция DML по какой-либо причине может занять некоторое время, не имеет смысла или даже легко определяется и понимается пользователем. Если вам когда-либо приходилось объяснять бухгалтеру блокировку против блокировки защелки (у меня есть), это сопоставимая ситуация.

Например, когда Боб «сообщает» приложению, что все сотрудники в отделе Боба начнут зарабатывать по 42 долл. / Мин 20160707 11:00:00, Боб (и его сотрудники) ожидают, что с этого времени зарплата каждого будет рассчитываться в 42 долл. / Мин. Бобу все равно, что для этого необходимо, чтобы приложение выполняло 2 чтения и 6 записей в базе данных на сотрудника, а их файлы данных и журналов располагаются на нескольких дисках RAID-5 SATA II, поэтому это занимает около 7 минут. чтобы завершить задачу для всех 256 сотрудников Боба. Боб, его бухгалтер и менеджер по начислению заработной платы, заботятся о том, чтобы все его сотрудники получали 42 доллара в минуту, начиная с 20160707 11:00:00. Иначе, сотрудники, которые были обновлены в, 20160707 11:00:01будут немного раздражены, в то время как те, чьи записи были обновлены в, 20160707 11:00:07будут собираться вне отдела начисления заработной платы.

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

SQLmojoe
источник
Хорошие очки. Я думаю, что технология подходит только для определенных случаев использования, таких как тот, который вы упоминаете. По причинам, которые я изложил выше, кажется, что было бы неправильно использовать для отслеживания цены или стоимости акций, которые могут измениться в очень короткие периоды времени.
Джеймс Андерсон
Вообще-то, нет. Это бесполезная проблема. Темпоральные таблицы по-прежнему работают, если вам нужно сохранить исторический момент цены акций. Вы просто должны убедиться, что вставки очень гранулированы и могут быть выполнены в очень маленьком окне. В противном случае последующие изменения будут заблокированы, и, если входящая скорость достаточно высока, произойдет тайм-аут и возможная потеря данных, если приложение не сможет обработать повторные попытки. Если вы запускаете БД без Fusion IO или с таблицами, оптимизированными для памяти, вы можете легко обрабатывать от десятков тысяч вставок в секунду до более ста тысяч в секунду.
SQLmojoe
3

Я считаю, что это действительно конструктивный недостаток, хотя и не специфический для SQL Server 2016, поскольку все другие существующие реализации временных таблиц (насколько я знаю) имеют такой же недостаток. Из-за этого могут возникнуть проблемы с временными таблицами; сценарий в вашем примере мягкий по сравнению с тем, что может пойти не так в целом:

Нерабочие ссылки на внешние ключи . Предположим, у нас есть две временные таблицы, а таблица A имеет ссылку на внешний ключ на таблицу B. Теперь предположим, что у нас есть две транзакции, каждая из которых выполняется с уровнем изоляции READ COMMITTED: транзакция 1 начинается до транзакции 2, транзакция 2 вставляет строку в таблицу B и фиксирует ее, затем транзакция 1 вставляет строку в таблицу A со ссылкой на вновь добавленную строку B. Поскольку добавление новой строки в B уже зафиксировано, ограничение внешнего ключа удовлетворяется, и транзакция выполняется 1 может совершить успешно. Однако, если бы мы просматривали базу данных «КАК ВСЕ» некоторое время между началом транзакции 1 и началом транзакции 2, то мы увидели бы таблицу A со ссылкой на строку B, которая не существует. Так что в этом случаевременная таблица обеспечивает несовместимое представление базы данных . Это, конечно, не было целью стандарта SQL: 2011, в котором говорится,

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

Неуникальные первичные ключи : допустим, у нас есть таблица с первичным ключом и две транзакции, обе с уровнем изоляции READ COMMITTED, в котором происходит следующее: после того, как транзакция 1 начинается, но до того, как она коснется этой таблицы, транзакция 2 удаляет определенное строка таблицы и коммитов. Затем транзакция 1 вставляет новую строку с тем же первичным ключом, который был удален. Это проходит нормально, но когда вы посмотрите на таблицу AS OF в промежуток времени между началом транзакции 1 и началом транзакции 2, мы увидим две строки с одинаковым первичным ключом.

Ошибки при одновременных обновлениях . Допустим, у нас есть таблица и две транзакции, которые обновляют в ней одну и ту же строку, снова на уровне изоляции READ COMMITTED. Транзакция 1 начинается первой, но транзакция 2 первой обновляет строку. Затем транзакция 2 фиксирует транзакцию, а транзакция 1 выполняет другое обновление строки и фиксирует транзакцию. Это нормально, за исключением того, что если это временная таблица, то при выполнении обновления в транзакции 1, когда система собирается вставить нужную строку в таблицу истории, сгенерированный SysStartTime будет временем начала транзакции 2, а SysEndTime будет временем начала транзакции 1, которое не является допустимым интервалом времени, так как SysEndTime будет перед SysStartTime. В этом случае SQL Server выдает ошибку и откатывает транзакцию (например, см.это обсуждение ). Это очень неприятно, поскольку на уровне изоляции READ COMMITTED не следует ожидать, что проблемы параллелизма приведут к прямым сбоям, а это означает, что приложения не обязательно будут готовы к попыткам повторных попыток. В частности, это противоречит «гарантии» в документации Microsoft:

Такое поведение гарантирует, что ваши унаследованные приложения продолжат работать, когда вы включите управление версиями системы для таблиц, которые выиграют от управления версиями. ( ссылка )

Другие реализации временных таблиц имели дело с этим сценарием (две параллельные транзакции обновляют одну и ту же строку), предлагая возможность автоматически «корректировать» временные метки, если они недействительны (см. Здесь и здесь ). Это уродливый обходной путь, поскольку он имеет печальное последствие нарушения атомарности транзакций, поскольку другие операторы в рамках одних и тех же транзакций обычно не будут корректировать свои временные метки таким же образом; т. е. с помощью этого обходного пути, если мы просматриваем базу данных «КАК ОТ» в определенные моменты времени, то мы можем видеть частично выполненные транзакции.

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

В контексте такого рода реализации я хотел бы предложить, чтобы до фиксации транзакции любые строки, добавляемые в таблицу истории, не были видны пользователю. С точки зрения пользователя, должно просто показаться, что эти строки добавляются (с отметкой времени фиксации) во время фиксации. В частности, если транзакция никогда не завершается успешно, она никогда не должна появляться в истории. Конечно, это несовместимо со стандартом SQL: 2011, который описывает вставки в историю (включая временные метки) как происходящие во время операторов UPDATE и DELETE (в отличие от времени фиксации). Но я не думаю, что это действительно имеет значение, учитывая, что стандарт никогда не был должным образом реализован (и, возможно, никогда не будет) из-за проблем, описанных выше,

С точки зрения производительности, системе может показаться нежелательным возвращаться назад и пересматривать строки истории, чтобы заполнить отметку времени фиксации. Но в зависимости от того, как это делается, стоимость может быть довольно низкой. Я не совсем знаком с тем, как SQL Server работает внутри, но PostgreSQL, например, использует журнал записи вперед, что делает его таким, что если несколько обновлений выполняются для одних и тех же частей таблицы, эти обновления объединяются, так что данные нужно записать только один раз на страницы физических таблиц - и это обычно применяется в этом сценарии. В любом слючае,

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

Брент Керби
источник
0

В момент совершения транзакции все данные должны быть записаны на страницах данных (в памяти и на диске в файле журнала). В том числе SysStartTimeи SysEndTimeколонны. Как узнать время окончания транзакции до ее фактического завершения?

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

jods
источник