Pull to refresh

Стартуем без транзакции. Альтернативный вариант вопросов на собеседовании «по SQL»

Level of difficultyMedium
Reading time29 min
Views13K

Введение

Меня зовут Иван, я занимаюсь программированием на T‑SQL и не только. Собеседования на позицию разработчика баз данных не только проходил, но и проводил. Предлагаю потенциальным кандидатам на аналогичную позицию познакомиться с вариантом проведения собеседования, который применяется не только мной. «ТОП-100» вопросов насобирать не получится, тут бы с одним разобраться.

Вынужден предупредить: статья отличается от некоторых других и вместо «загадок» перечень «отгадок» не предоставляет. Решение сформулированных задачек остаётся на вашей стороне, надеюсь, найдете среди них полезные.

Специфика статьи:

  • диалект — Transact SQL

  • СУБД — Microsoft Sql Server

  • позиция кандидата — разработчик баз данных

  • грейд — перспективный джун или крепкий мидл

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

Необязательное многословное вступление

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

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

Тема статьи — какие собеседования бывают в действительности и что за вопросы на них задают. Содержание будет отражать лично мой подход, опыт. Моё видение, в котором вполне могут быть изъяны. Но, уверяю, большинство вменяемых (это нелишняя ремарка) собеседований на позицию разработчика баз данных проводятся, может, и по другой схеме, в другом стиле, однако затрагивают те же тематические блоки.

Максимально универсальную статью про вообще весь SQL написать не удастся — не позволит квалификация. Лёгким движением руки и без дополнительных объявлений в тексте перескакивать с особенностей мускуля на синтаксис посгреса, объяснять суть вещей оракла цитатами из документации сиквела могут только настоящие гуру (пардон, не удержался).

Целевая аудитория — те, кому нужно сориентироваться, кем «по SQL» вы хотите устроиться работать. Для прохождения собеседования на вакансию, в описании которой нашлись вот эти три весёлых буквы, это важно. Если хотите в разработку OLTP, то обозначенные темы вполне могут помочь подготовиться получше. Не вызубрить, а именно разобраться в том, как работает СУБД и как, собственно, будет себя вести вами же написанный код. В статье также могут найти для себя полезное те, кто занимается в каком-то виде тестированием T-SQL кода. Для разработки отчётов, анализа данных, написанное ниже, скорее бесполезно, чем наоборот. Для администрирования баз данных — в лучшем случае косвенно относится к предмету деятельности, но точно не самое важное для собеседования. Для поддержки эксплуатируемых систем синтаксис и принципы работы СУБД, особенности языка важны, но взгляд нужен под другим углом и на собеседовании, скорее всего, акценты будут расставлены иначе.

О себе вынужден отметить, что сертификатов MS не имею, не преподавал, но проведением собеседований занимался. Хотя последнее провёл в 2017–2018м году. Поэтому в тексте могут быть ошибки и неточности, связанные не только с невнимательностью при написании статьи, но и с объективно недостаточными познаниями и опытом. В качестве соискателя, очевидно, тоже прошёл через десятки собеседований.

Когда-то давно для проведения собеседований пытался соорудить некий опросник-задачник. За пример брал (ожидаемо) то, что раньше сам получал в виде задания на собеседованиях: а) простенькая задачка б) задачка на внимательность в) нормальные формы г) индексы и оптимизация. Плюс-минус. Не готов утверждать, что такой подход бессмысленный или бесполезный — есть положительные стороны, как и недостатки. Вордовский файлик с задачками возможно дать сильно заранее или передать с HR-специалистом, чтобы, когда вы присоединитесь к беседе, кандидат уже подготовился. Если шаблонный опросник сформирован, то его можно обсуждать и дорабатывать. Мне вот до сих пор кажется удачной находкой вопрос "что такое оптимальный индекс?" Кандидату, вполне возможно, тут же в голову приходила мысль,- «хоспаде, опять неадекваты...», но, с другой стороны, через минуту становилось ясно, инженерный ли склад ума у потенциального разработчика и действительно ли кандидат понимает суть ответов, которые давал на предыдущие вопросы. В комментариях попробуйте предположить, что же такого должен сказать кандидат, чтобы ответ зачёлся как правильный.

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

Структура собеседования

  • ACID

  • "Напишите вот такой селект"

Всё, два пункта. Два варианта развития беседы.

ACID

Нет, интересует, естественно, не расшифровка аббревиатуры, не перевод на русский и не цитирования фраз про "всё или ничего". Если кандидат не знаком с аббревиатурой и даже после того, как вы сами её расшифровали и перевели, "ничего в голове не щёлкнуло" — не страшно. Важно наличие понимания, как работает СУБД, как ведёт себя написанный на T-SQL код, и всё это можно рассказать своими словами. Вы прочитали статьи, упомянутые выше, видели там определение ACID и выучили наизусть? Сейчас узнаем, как это вам пригодится.

Atomicity

Окей, Карл, ты вы написали хранимку, которой можно передать несколько параметров. В теле хранимки есть набор команд: INSERT, UPDATE, DELETE, в конце SELECT. Абсолютно неважно, о чём речь... пусть будет хранимка для фиксации факта покупки.

Псевдокод
CREATE PROC dbo.save_committed_buy_order
    <какие-то параметры>
AS
BEGIN
    BEGIN TRAN
    
    INSERT dbo.my_orders ...
    
    UPDATE dbo.my_balance ...
    
    DELETE dbo.my_basket ...
    
    SELECT @order_id as new_order_id
    
    COMMIT TRAN
END

Дело не в конкретных таблицах и не в синтаксисе, а в том, зачем тут транзакция. Вот написали такую хранимку, протестировали на DEV-сервере с одними параметрами, с другими — всё прекрасно работает. Все команды каждый раз отрабатывают успешно: появляется строчка в таблице сделанных заказов, обновляется баланс, очищается корзина, возвращается номер зарегистрированного заказа.

Теперь вопрос: а транзакция-то здесь зачем?

Всё же отрабатывает. Проблем никаких нет. "Всё или ничего" — всегда "всё". Шикарный код, до блеска отполированный тестированием. Может, транзакция тогда не нужна? А если нужна, то зачем?

Внезапно этот вопрос ставит в тупик чуть менее, чем до хрена соискателей. При этом "ACID" некоторые очень даже расшифровывают.

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

Самый популярный ответ: внезапное отключение питания сервера.

Как говорится, ответ верный, но неправильный. Максимально неправильный. Произойти может и такое, но это очень далеко от написанного нами кода и чересчур грандиозно.

Попробуем добраться до сути. Представьте, что в одном из параметров нам передали product_id — идентификатор купленного товара. Мы это значение хотим вставить в таблицу dbo.my_orders. А по какой-то причине product_id передали такой, которого нет в справочнике продуктов. Если мы где-то храним ссылки на продукты, то у нас, конечно же, есть а) справочник продуктов б) внешний ключ для контроля целостности. И при попытке вставить в dbo.my_orders какого-то непонятного product_id возникнет ошибка нарушения целостности по FK.

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

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

Итак, мы выяснили, что кривые данные приводят (способны привести) к нарушению целостности. Продолжаем "странные" вопросы: и что произойдёт в описанной ситуации? Выполнение команд прекратится? Если рассматриваем вариант с оборачиванием пакета команд в транзакцию, то транзакция откатится?

Для тех, кто дочитал до этого момента, даю фрагмент кода для экспериментирования
CREATE TABLE dbo.products
(
  product_id     INT NOT NULL PRIMARY KEY
  , product_name VARCHAR (100) NOT NULL
)

CREATE TABLE dbo.my_orders
(
  order_id INT NOT NULL IDENTITY(1,1) PRIMARY KEY 
  , customer_id INT NOT NULL
  , product_id INT NOT NULL CONSTRAINT FK_products FOREIGN KEY REFERENCES dbo.products(product_id)
  , volume INT NOT NULL
)

CREATE TABLE dbo.my_balance
(
  customer_id INT NOT NULL PRIMARY KEY
  , volume   DECIMAL(18, 2) NOT NULL
  , CONSTRAINT CK_my_balance_volume_not_negative CHECK (volume >= 0)
)

CREATE TABLE dbo.my_basket
(
  customer_id INT NOT NULL,
  product_id INT NOT NULL,
  amount INT NOT NULL
)

INSERT dbo.products(product_id, product_name)
VALUES(1, 'toothpaste')

INSERT dbo.my_balance(customer_id, volume)
VALUES(100, 300)

INSERT dbo.my_basket(customer_id, product_id, amount)
VALUES(100, 1, 50)

CREATE OR ALTER PROC dbo.save_committed_order
    @customer_id INT,
    @product_id INT,
    @amount INT,
    @price DECIMAL(18,2)
AS
BEGIN
    BEGIN TRAN
    
    INSERT dbo.my_orders(product_id, customer_id, volume)
    VALUES (@product_id, @customer_id, @amount * @price)
    
    UPDATE bal SET
        volume -= @amount * @price
    FROM dbo.my_balance bal
    WHERE bal.customer_id = @customer_id
    
    DELETE bsk
    FROM dbo.my_basket bsk
    WHERE customer_id = @customer_id
    
    SELECT SCOPE_IDENTITY() as new_order_id
    
    COMMIT TRAN
END

EXEC dbo.save_committed_order
    @customer_id = 100,
    @product_id = 3,
    @amount = 3,
    @price = 50

SELECT * from dbo.my_balance

SELECT * from dbo.my_orders

SELECT * from dbo.my_basket

TRUNCATE TABLE dbo.my_orders
TRUNCATE TABLE dbo.my_balance
TRUNCATE TABLE dbo.my_basket

Если нет локального инстанса MSSQL, то можно погонять вот здесь https://sqliteonline.com/

И внезапно выясняется, что severity ошибки про нарушение FK недостаточен для прерывания пакета команд. Да и транзакция каким-то образом оказывается зафиксированной.

Кстати, пользуясь случаем передаю привет тем, кто противопоставляет батч транзакции; приходите в комментарии, объясните, что имелось в виду).

С одной стороны, на уровне конкретной таблицы нарушения целостности не случилось. С другой стороны, у нас был целостный набор команд, из которого оказались фактически успешно выполненными лишь часть, БД приведена в рассогласованное (не целостное) состояние и вместо "всё или ничего" мы получили "что попало — и сойдёт". И где же здесь Atomicity? СУБД сломалась или мы что-то сделали не так?

В предыдущем примере мы заказ не оформили, а деньги с баланса списали. Попробуем другой вызов:

EXEC dbo.save_committed_order
    @customer_id = 100,
    @product_id = 1, -- fixed
    @amount = 3,
    @price = 150 -- modified

Как поведёт себя код в этом случае?

Идентификатор продукта в этот раз корректный. Ошибка возникает на апдейте баланса, поскольку 150 * 3 > 300. При попытке вычесть сумму заказа из баланса возникает нарушение CHECK-констрейнта. Северити этой ошибки такой же, как у нарушения FK, в итоге мы заказ оформили, а с баланса ничего не списали. Что одна, что другая ситуация с точки зрения гипотетической реальной системы неприемлема.

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

Как же нам в этой хранимой процедуре обеспечить ту самую атомарность?

Вопрос, конечно же, про обработку ошибок. Здесь ключевые слова, которые пригодятся кандидату — XACT_ABORT и TRY-CATCH. Если кандидат — уже опытный боец и первым делом захотел рассказать о повсеместной проверке @@ERROR, то имейте в виду, что собеседователь может не выдержать груза будущего обоюдно мучительного переучивания вас с IF @@ERROR != 0 GOTO ERR на актуальную версию T-SQL и современные подходы, тут же на собеседовании от этой тяжести заболеет и умрёт. И некому будет взять вас на работу. Где-то @@ERROR может и пригодиться: в работе с каким-нибудь внешним старьём, древними системными хранимками или в ковыряниях с SQLDMO. Но в целом в разработке такой подход не применяется с момента появления в сиквеле конструкции TRY-CATCH.

Уточняющие вопросы, которые могут возникнуть, если бодро расскажете о XACT_ABORT и TRY-CATCH:

  • а что будет с внешней транзакцией при XACT_ABORT ON, установленном в хп?

  • как в CATCH понять, есть у нас что откатывать или транзакция уже принудительно закрыта?

  • возможен ли сценарий коммита, если мы уже свалились в CATCH?

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

Если обсуждаемая хранимка не виртуальна, а написана на бумаге или в SSMS, то у кандидата, который действительно раньше программировал на T-SQL или прошёл хорошие курсы, должны/могут сработать рефлексы на:

  • отсутствие SET NOCOUNT ON

  • отсутствие RETURN с различающимся значением в случае успеха и неуспеха

Это уже больше про аккуратность и внимательность, но собеседующий может для себя отметить, что реакции не было. Хотя пример вроде как не про это и он достаточно "сферический в вакууме". Я бы, скорее, на месте кандидата и не подумал, что эту каляку-маляку имеет смысл причёсывать. Но собеседователи попадаются разные. Ещё могут спросить, почему использован SCOPE_IDENTITY(), а не @@IDENTITY и чем они отличаются.

Consistency

Про целостность уже затронули - выше упоминались и внешние ключи (FK), и чеки (CHECK). И мы специально сгенерировали ошибки, которые продемонстрировали, что СУБД не даст попортить данные, если для их контроля навешано что-то декларативное, но при этом общая согласованность данных БД — задача программиста.

Можно бы здесь ещё ввернуть пример с вознёй в триггерах, но инструмент этот своеобразный и, чтоб вы понимали, когда это зависело от меня, никто в систему ни одного DML-триггера затащить не смог. В нескольких последних коллективах, где это не от меня зависело, придерживались того же мнения и "на триггерах" систему не строили. Тема "за триггеры" холиварная, статья не об этом; доказывать в комментариях ничего не нужно, вопросов не имею. На текущем месте этого "г'добра" завались. Но примерчик для статьи сочинять неинтересно.

Вопросы целостности в том или ином виде ещё окажутся затронуты ниже.

Isolation

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

Вот мы сделали BEGIN TRAN и дальше запустился UPDATE, который должен уменьшить баланс на сумму покупки. Но мы же не одни на сервере. Параллельно работают многие другие транзакции, которые селектят или апдейтят данные, в том числе ту же самую строчку, которую мы пытаемся модифицировать в своей затянувшейся транзакции.

Представим, что баланс клиента 123 равен 500. Мы вызвали:

BEGIN TRAN

UPDATE bal SET
  volume -= 100
FROM dbo.my_balance bal
WHERE bal.customer_id = 123

и транзакция ещё не завершена (неважно, по какой причине). Теперь мы в другой транзакции (например, в другой вкладке SSMS) делаем:

SELECT * FROM dbo.my_balance bal
WHERE bal.customer_id = 123

Какой результат мы получим?

И снова чуть менее, чем до хрена кандидатов затягивают драматичную паузу.

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

А ответ на заданный вопрос всё ещё можно найти в самом определении ACID. Транзакции друг от друга изолированы. И в данном конкретном случае мы никакого результата не увидим, пока транзакция с апдейтом не завершится. Наша вторая транзакция будет ожидать возможности навесить Shared-lock и будет ждать, пока оттуда первая не снимет Exclusive-lock. О дебрях совместимостей блокировок навряд ли спросят, разве что в качестве задачки со звёздочкой, но знать, в чём физически выражается изоляция и как себя ведёт код при том самом "грязном чтении", при REPEATABLE READ нужно. Вам же известны наизусть уровни изоляции, история про фантомные чтения? Так это вопрос как раз о них. Только здесь челлендж не в чтении на скорость вызубренного, а в том, понимаете ли вы, как это фактически себя проявляет в работе T-SQL кода и как поведением кода с помощью этих уровней изоляции управлять.

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

Дополним вопрос о блокировках:

BEGIN TRAN

SELECT * FROM dbo.my_balance bal
WHERE bal.customer_id = 123

А вот так, что произойдёт с параллельной транзакцией? Shared-lock же накладывается при чтении? Накладывается. Мы в другой транзакции сможем сделать такой же селект? Будет другая транзакция ждать закрытия этой? А проапдейтить в другой транзакции сможем?

То есть разворачиваем предыдущий пример задом наперёд. В первой вкладке SSMS открываем транзакцию и селектим, а во второй апдейтим. Что произойдёт?

BEGIN TRAN

SELECT * FROM dbo.my_balance bal
WHERE bal.customer_id = 123

WAITFOR DELAY '00:00:20'
-- после запуска этого кода в соседней вкладке выполните UPDATE той же строки

SELECT * FROM dbo.my_balance bal
WHERE bal.customer_id = 123

В первой вкладке происходит формирование строгого отчёта и ниже выполняется для какой-то цели команда с выборкой из этой же таблицы той же строки с customer_id = 123. И мы в транзакции с чтениями сначала получили один вариант данных, а ниже по коду — совершенно другой. Испорченный апдейтом.

И что делать с таким отчётом, который в одном месте показывает "ваш баланс = 500", а в другом — "в смысле, 400"? Транзакцию ведь перед чтением открыли, всё должно быть атомарно и изолировано. Опять ACID в СУБД сломался? Так как обеспечить повторяемость результатов чтения одних и тех же данных в читающей транзакции?

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

Код хранимки
CREATE OR ALTER PROC dbo.transfer_money
    @from_customer_id INT,
    @to_customer_id INT,
    @volume DECIMAL(18,2)
AS
BEGIN
    SET XACT_ABORT ON;
    
    BEGIN TRAN;

    UPDATE bal SET
        volume -= @volume
    FROM dbo.my_balance bal
    WHERE bal.customer_id = @from_customer_id

    UPDATE bal SET
        volume += @volume
    FROM dbo.my_balance bal
    WHERE bal.customer_id = @to_customer_id
    
    COMMIT TRAN;
END

INSERT dbo.my_balance(customer_id, volume)
VALUES(2, 5000)

INSERT dbo.my_balance(customer_id, volume)
VALUES(3, 100)

EXEC dbo.transfer_money
    @from_customer_id = 2,
    @to_customer_id = 3,
    @volume = 2000

SELECT * FROM dbo.my_balance ORDER BY customer_id

Вызываем, видим что у одного кастомера вместо 5000 теперь 3000, а у другого вместо 100 теперь 2100. Тривиальнейший пример, элементарная арифметика. И мы полностью разобрались в ACID. Теперь добавим между апдейтами задержку на 20 секунд WAITFOR DELAY '00:00:20', обновим хранимку и снова откроем две вкладки в SSMS. В первой сделаем ещё один перевод 2000:

EXEC dbo.transfer_money
    @from_customer_id = 2,
    @to_customer_id = 3,
    @volume = 2000

а во второй (одновременно) — перевод 100 в обратную сторону:

EXEC dbo.transfer_money
    @from_customer_id = 3,
    @to_customer_id = 2,
    @volume = 100
    
SELECT * FROM dbo.my_balance ORDER BY customer_id

Получилось 1100 и 4000? Нет, получился deadlock. Одна из транзакций оказалась выбрана жертвой и закоммитилась только другая.

Если дэдлок не получается, то, возможно, у вас выставлен неподходящий для примера уровень изоляции и, возможно, отключено автосоздание статистики. Попробуйте добавить в начало ХП SET TRANSACTION ISOLATION LEVEL READ COMMITTED а в апдейты — WITH (ROWLOCK). Но, по идее, со стандартными настройками БД по умолчанию всё должно получиться без дополнительных телодвижений. Более надёжный вариант, например, с двумя таблицами, можно было бы сочинить, но он не был бы настолько же вызывающе примитивен.


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

Для объяснения, что такое дэдлок, кандидату снова понадобятся знания об уровнях изоляции и блокировках. И, конечно же, прозвучит вопрос о том, что с этими дэдлоками делать. Как писать код, чтобы он работал, а не валился постоянно с ошибками.

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

У аксакалов иногда возникают вьетнамские флешбэки, что уводит их мысли к желанию победить дэдлоки враз и навсегда - через APP LOCK. Но я бы предостерёг предлагать такие идеи на собеседовании. За превращение сервера БД в однопоточный, последовательный механизм никто в реальном проекте спасибо не скажет. Да, подобная техника существует и иногда применяется, но только как костыль вокруг чего-то совсем ужасного. И нередко это связано с решением, которое должно было быть реализовано где угодно, только не в БД.

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

И индексы, конечно. И индексы. Есть индекс - быстро блокируем и конкретные строки. Нет индекса - долго сканим и лочим слишком много. Жадные и медленные транзакции - эт плохо, пнятно ©. Мне об индексах на этом уровне собеседования больше подробностей не нужно. Но каждый собеседователь предпочтет подойти к этой теме по-своему. Классическое "чем кластерный отличается от некластерного" вполне может прозвучать.

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

Один из действенных способов снизить конкуренцию между транзакциями - не блокировать зря. Это не только про построение индексов, чтобы блокировать одну строчку вместо таблицы целиком, но и про то, чтобы не блокировать совсем ничего, если можно заранее понять, что операция обречена. В модификацию данных можно не заходить, если заранее обнаружить проблему в переданных параметрах или в неподходящем состоянии целевых данных. И здесь же будет уместно рассказать о разнице в поведении THROW и RAISERROR. Дополнить предложенные выше рассуждения о XACT_ABORT, TRY-CATCH и XACT_STATE.

Из разговора об изоляции транзакций внезапно возвращаемся к вопросам поддержания целостности. Вот FK, который в первом примере не дал вставить строчку в заказы с кривым product_id = 3. Такого идентификатора не было в справочнике продуктов. На этот справочник смотрел задекларированный внешний ключ и не дал привести данные таблицы заказов в неконсистентное состояние.

А как он это сделал, за счёт чего?

И это вопрос довольно существенный. Из разряда "а не создать ли нам индексы на все случаи жизни, ведь индекс - это хорошо". Вот мы завели самый базовый справочник dbo.my_users. Из множества таблиц, где хранятся всякие author_id, recepient_id, boss_id навесили десятки, сотни FK на этот справочник. Нет ли у этого подхода изъянов, не платим ли мы какую-нибудь цену за применение FK? А вот и платим. И можно нехитро соорудить пример БД, в которой из справочника вроде описанного dbo.my_users будет невозможно удалить даже одну строку. Вот никак. СУБД надолго озадачится, будет пыхтеть и кряхтеть, но ничего, кроме странных ошибок не выдаст.

Объяснение довольно простое. Одна транзакция вставляет строчку в dbo.my_orders со ссылкой на продукт 1, а другая... а другая вполне может в это же время заниматься удалением этого продукта. И каким образом ACID-compliant СУБД должна обеспечить в этой ситуации целостность? Да теми же механизмами изоляции одной транзакции от другой. Чтобы вставить строчку в зависимую таблицу, придётся сходить во все справочники, на которые та ссылается, убедиться, что там есть запись с нужным идентификатором, навесить блокировку от удаления и держать до конца транзакции. А при удалении из справочника нужно сходить во все зависимые таблицы, убедиться, что там нет ссылающихся на данный идентификатор записей и не дать такие записи вставить до конца транзакции удаления. И это будет не факт, что легко. Заучивать этот абзац нет никакого смысла, он написан только для того, чтобы а) возник интерес в том, чтобы по-настоящему разобраться в механике работы ACID-СУБД б) чтобы постараться развеять ореол магической магии вокруг некоторых "внутренних механик" СУБД.

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

Теперь задачка со звёздочкой: можно ли получить дэдлок через FK?

Ну то есть вы модифицируете в параллельных транзакциях данные только в одной из двух соединённых FK таблиц, а дэдлок возникает. А вы "второй ресурс" не запрашивали. Вроде бы не на чем дэдлоку возникнуть.

А на одной таблице, когда FK смотрит на саму себя же через связь вида parent_id -> id? Возможен ли такой дэдлок, как его соорудить и чем лечить?

В дополнение к апокалиптическому "отключению питания" и дэдлокам также можно добавить ошибку Lock Timeout. Да, случается и такое. Дэдлока не возникло, но ожидание кем-то занятого ресурса длилось слишком долго. И выполнение операции оказалось прервано.

Durability

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

Долговечность/надёжность/гарантированность заключается в том, что если уж СУБД приняла команду COMMIT TRANSACTION и успешно её отработала, то внесённые изменения точно зафиксированы, записаны в журнал транзакций и сохранены на диск. Если после коммита произойдёт то самое "внезапное отключение питания сервера", то потом не выяснится, что часть модифицированных строк были только в некоей очереди отложенных записей, что якобы закоммиченные изменения где-то подзависли в специальном внутреннем кэше. Нет, если закоммитили, значит, гвоздями к жёсткому диску приколотили. ACID-compliant СУБД это гарантирует.

В недавних статьях, кроме прочего, мелькали утверждения о необратимости TRUNCATE TABLE. СУБД существует больше одной, и они работают по-разному.

CREATE TABLE #test (title VARCHAR(100))

INSERT #test (title)
VALUES ('one')

BEGIN TRAN

TRUNCATE TABLE #test

ROLLBACK TRAN

SELECT * FROM #test

Команда TRUNCATE в MS SQL Server минимально, но логируется в журнал транзакций. А значит, эта транзакция подчиняется всё тем же ACID принципам и, чтобы вызванные изменения оказались "приколоченными гвоздями к диску", транзакцию нужно закоммитить. А ROLLBACK, очевидно, может сказать журналу "горшочек, не вари" и изменения откатятся как и любые другие. Исключением из принципов ACID эта команда не является. Эти принципы — основополагающие для понимания того, как конкретная СУБД работает. Безусловно, есть множество самых разных нюансов реализации, применённых трюков во имя борьбы за оптимизацию, особенностей конкретных команд, элементов языка. Можно чего-то не знать, не уметь, главное — не стать любителем охоты за мифами и городскими легендами.

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

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

И раз уж вспомнили о городских легендах, окончательно разобрались с ACID, рассмотрим такой пример. Табличные переменные, которые ни в какой "в памяти" перманентно и безальтернативно не живут, минимально, но логируются. То есть операции модификации данных табличной переменной вполне можно обнаружить в журнале транзакций. Похоже на особенности работы TRUNCATE, не так ли?

CREATE TABLE #test (title VARCHAR(100))
DECLARE @test TABLE (title VARCHAR(100))

BEGIN TRAN

INSERT #test (title)
VALUES ('one')

INSERT @test (title)
VALUES ('two')

ROLLBACK TRAN

SELECT * FROM #test
SELECT * FROM @test

Что получим в результате? Но как же так, ведь мы всё откатили! Опять сломался ACID.

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

В блоке "за дюрабилити" уместно также затронуть и некоторые другие темы. В том числе, будущие кандидаты, если вы фактически на предыдущем месте кодили не особо активно, то, скорее всего, не самым удачным образом ответили на вопросы, заданные выше. Но если хоть в каком-то виде админили и саппортили, то, возможно, знаете разницу между SIMPLE и FULL, про ALWAYS ON или репликацию. О бэкапировании и восстановление базы из бэкапа на точный момент времени. Даже если напрямую не спрашивали - обязательно расскажите об этом опыте. Это всё тоже важно. В небольших фирмах прокачать скилл кодирования редко удаётся, а вот быть на все руки мастером приходится. И с нуля сервак поднять, и мэйнтенанс планы настроить. Это полезный опыт, который имеет смысл упомянуть.

И, раз уж вспомнил здесь пример из первого раздела про Atomicity и заговорили за бэкапы, то дополним ситуации, которые способны привести к внезапному прекращению выполнения операций в хранимке. Кроме отключения питания сервера или внезапного погашения инстанса, есть не менее грандиозные, но чуть более прозаические вещи. Может кончиться место и некуда будет писать журнал транзакций. Или в настройках файловых групп забыли выставить опцию автоувеличения файлов и место вроде как есть, но файл съел весь выделенный размер, и сам не может увеличиться. Если вы не завернули множественные операции модификации данных в транзакцию, и запуск прервался страшным событием вроде исчерпания свободного места, то после возобновления нормальной работы сервера обнаружится именно то, что только часть модификаций из пакета команд оказалась применена. И данные БД находятся в рассогласованном (с точки зрения бизнес-правил системы) состоянии. А была бы транзакция — сработал бы принцип "всё или ничего".

Неожиданный поворот:
А если DML-операций не несколько, а одна. И вы её не завернули явно в транзакцию. Но операция производится над большим объёмом данных, и это занимает длительное время. Допустим, вы делаете UPDATE миллиона строк и на пятьсот тысяч первой строке возникает ошибка нарушения констрейнта или любая другая из рассмотренных выше.

  • Продолжит ли UPDATE колбасить дальше и в итоге не затронет только проблемные строки?

  • Если прервётся, то в изменённом или первоначальном виде останутся первые 500 000 строк, с которыми проблем не возникло?

  • А если на таблицу смотрит триггер и когда реагирует на изменения, пишет в дополнительную таблицу, например, историю операций, то что случится с этой историей операций?

  • А если этот UPDATE завернуть в хранимку (всё ещё без открытия явной транзакции), то поведение изменится?

  • А если в хранимке выставить XACT_ABORT в ON? А если в OFF?

  • Ваши ответы актуальны для конфигурации, когда IMPLICIT_TRANSACTIONS выставлено в ON или в OFF? Если есть разница, то в чём она заключается?

Те же вопросы для операций INSERT и DELETE.

В документации ответы есть. Осталось открыть SSMS, поэкспериментировать, на практике убедиться в верном понимании прочитанного.

Окей, финального босса победили, из него выпал артефакт с бонусом против магии дюрабилити. Заходим в призовую локацию.

Такой запрос:

CREATE TABLE dbo.test_insert (id INT, dt DATETIME)
GO
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT, XACT_ABORT ON;

DECLARE @i INT = 10000, @dt DATETIME2(7) = SYSDATETIME()

BEGIN TRAN

WHILE @i > 0
BEGIN
    INSERT dbo.test_insert(id, dt)
    VALUES (@i, GETDATE())

    SET @i -= 1
END

COMMIT TRAN

PRINT DATEDIFF(MS, @dt, SYSDATETIME())
GO

DROP TABLE IF EXISTS dbo.test_insert
GO

Почему с транзакцией работает за сущие копейки © считаные миллисекунды, а если убрать BEGIN TRAN/COMMIT, то уже несколько секунд?

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

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

Для временных таблиц проблема актуальна? А для табличных переменных? Ответить можно было без дополнительных прогонов переделанного примера — выше нюансы работы одного и другого рассматривались.

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

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

Переделанная цитата из фильма "Master and Commander".

Подведём итог

Итак, вы написали хранимку, которой можно передать некоторые параметры. В теле хранимки есть несколько команд: INSERT, UPDATE, DELETE, SELECT. Вы протестировали этот код на DEV-сервере с разными наборами параметров — всё прекрасно работает. Все выражения каждый раз отрабатывают успешно: появляется строчка в таблице сделанных заказов, обновляется баланс, очищается корзина, возвращается номер зарегистрированного заказа.

А если всё и так работает, то зачем вокруг этих команд нужна транзакция?

"Напишите вот такой селект"

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

Но если вы сюда добрались, то, возможно, вам пригодится более-менее причёсанный вариант хранимки из начала статьи:

Код
CREATE OR ALTER PROC dbo.save_committed_order
    @customer_id  INT,
    @product_id   INT,
    @amount       INT,
    @price        DECIMAL(18,2),
    @new_order_id INT = NULL OUTPUT
AS
BEGIN
    SET NOCOUNT, XACT_ABORT ON;
    
    DECLARE
        @external_tran_count  INT = @@TRANCOUNT
        , @deadlock_retries   TINYINT = 3
        , @savepoint          VARCHAR(128) = 'save_committed_order_tran'
        , @err_msg            VARCHAR(2000);
    
    IF ISNULL(@amount, 0) <= 0
    BEGIN
        SET @err_msg = FORMATMESSAGE('Недопустимое количество товара: %d', @amount);
        THROW 50000, @err_msg, 1;
    END
    
    IF ISNULL(@price, 0) <= 0
    BEGIN
        SET @err_msg = FORMATMESSAGE('Недопустимая цена товара: %s', CAST(@price AS VARCHAR(20)));
        THROW 50000, @err_msg, 2;
    END
    
    IF NOT EXISTS(SELECT 1 FROM dbo.customers WHERE customer_id = @customer_id)
    BEGIN
        SET @err_msg = FORMATMESSAGE('Неизвестный ID покупателя: %d', @customer_id);
        THROW 50000, @err_msg, 3;
    END
    
    IF NOT EXISTS(SELECT 1 FROM dbo.products WHERE product_id = @product_id)
    BEGIN
        SET @err_msg = FORMATMESSAGE('Неизвестный ID продукта: %d', @product_id);
        THROW 50000, @err_msg, 4;
    END
    
    IF NOT EXISTS(SELECT 1 FROM dbo.my_balance bal WHERE bal.customer_id = @customer_id AND volume >= @amount * @price)
    BEGIN
        SET @err_msg = 'Недостаточно средств';
        THROW 50000, @err_msg, 5;
    END
    
    BEGIN TRY
        IF EXISTS(SELECT 1 FROM dbo.basket WHERE customer_id = @customer_id)
        BEGIN
            DELETE bsk
            FROM dbo.basket bsk
            WHERE bsk.customer_id = @customer_id;
        END
        
        IF @external_tran_count = 0
            BEGIN TRAN;
        ELSE
            SAVE TRAN @savepoint;

        -- "на всякий случай" подобное не делается
        -- и "в универсальный шаблон CRUD-хп" не добавляйте
        -- и бойтесь тех, кто так пишет постоянно
        WHILE @deadlock_retries > 0
        BEGIN
            BEGIN TRY
                UPDATE bal SET
                    volume -= @amount * @price
                FROM dbo.my_balance bal
                WHERE bal.customer_id = @customer_id;
            
                BREAK;
            END TRY
            BEGIN CATCH
                IF ERROR_NUMBER() = 1205 AND XACT_STATE() <> -1
                AND @deadlock_retries > 1
                    SET @deadlock_retries -=1
                ELSE
                    THROW;
            END CATCH
        END
        
        INSERT dbo.my_orders(product_id, customer_id, volume)
        VALUES (@product_id, @customer_id, @amount * @price);
        
        SET @new_order_id = SCOPE_IDENTITY();
        
        IF @external_tran_count = 0
            COMMIT TRAN;
    END TRY
    BEGIN CATCH
        IF XACT_STATE() <> 0
        BEGIN
            IF @external_tran_count = 0
                ROLLBACK TRAN;
            ELSE
            IF XACT_STATE() = 1
                ROLLBACK TRAN @savepoint;
        END;
        
        THROW;
    END CATCH

    RETURN 0;
END

Сорян, не тестировал.

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

Ещё вроде бы популярен такой формат как "разбор фильма" — было бы здорово увидеть от опытных разработчиков разбор этого суррогатного примера с объяснением, почему такой порядок проверок, такой порядок внесения изменений в таблицы, зачем конкретные операторы, в каких ситуациях SELECT @new_order_id менее удобен, чем предложенная версия с OUTPUT-параметром. И что можно порекомендовать в качестве альтернативы любого из принятых решений. Что лишнее, какие допущены ошибки.

Или такая задачка, чтобы размяться: перепишите на мягкий вариант без генерации исключений (даже если таковые возникли в TRY-CATCH), а с разными кодами возврата и логированием сути обнаруженной проблемы в таблицу. И тоже с учётом возможного присутствия внешней транзакции.

Советы

Ещё пара советов для прохождения собеседования на разработчика БД:

  • Синтаксис создания таблицы и создания хранимки должен вам быть хорошо известен. Необязательно знать хитрые варианты, все возможные опции, но базовый синтаксис выражений CREATE TABLE, ALTER TABLE, CREATE PROCEDURE не должен вызывать никаких сложностей.

  • После просьбы создать таблицу начать клацать мышкой в дизайнере таблиц — не успеете моргнуть, как вас вместе с поражённым этим странным заболеванием компьютером уже вытолкают в ближайшее окно

  • После просьбы создать хранимку начать клацать в менюшках SSMS, чтобы оно выдало шаблон хп — половицы раздвигаются, и вы падаете в яму с аллигаторами

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

К слову, однажды, спустя много лет пользования SSMS, от одного из бэкендеров узнал такое (случайно глазами заметил его действия в SSMS): если написать простой SELECT из таблицы, то прямо в гриде, в резалтсете можно вносить изменения в данные, как при редактировании ячейки в Excel. И SSMS сама проапдейтит таблицу. Ни в коем случае так не сделайте! И лучше не делайте никогда ("всё, забыли, ушла история!" ©). Если просят внести изменения в данные, то уж будьте уверены, от вас ждут написание выражения UPDATE.

Вступительное слово на собеседовании

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

1. Начните не с допроса

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

2. И им не продолжайте

Если вам по-настоящему нужен человек-справочник, обладатель феноменальной памяти, то обязательно распечатайте несколько страниц документации по встроенным функциям, по Database Engine и сверяйте по ним ответы кандидата. Если же вам нужен разработчик — человек, генерирующий идеи, синтезирующий из знаний о возможностях языка и СУБД, понимания принципов их работы, решения не всегда ординарных задач, то, возможно, проверять запоминание чего-то наизусть не стоит. Проверьте знание принципов и понимание проблематики, способность выбрать инструмент для решения задач.

Не помнит аббревиатуру ACID? Расшифруйте. Не сдвинулись с места? Так бывает. Загруженному практикой бывает некогда настолько далеко возвращаться к "базе", к теории. Узнайте, что хотите узнать у кандидата, заходом через решение практических задач. Через проблематику, хорошо знакомую активно практикующим разработчикам.

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

  • перечислить все функции работы со строками,

  • рассчитать на калькуляторе физический размер строки для такого-то определения таблицы,

  • назвать конкретные номера ошибок и точное значение их северити,

  • нарисовать по памяти таблицу совместимости типов для неявного преобразования

  • изобразить карандашом на бумаге оценочный план выполнения предложенного запроса со всеми операторами

Будете давить на зубрёжку — получите зубрильщиков. Узнаете, что нередко те, кто "всё выучили" и готовы ответить на все вопросы экзамена "на пятёрку", не очень-то способны воспользоваться этим навсегда зафиксированным в памяти материалом как-то иначе.

И вспомните себя. Как часто приходится обращаться к гуглу и StackOverflow с вопросами уровня "да как оно там пишется?.." Я вот давно отчаялся запомнить, в каком порядке что передавать в DATEDIFF. В моменте-то всё ясно, но если вылетело из головы, то без документации не восстановится. Периодически забавно выглядят попытки вспомнить: DBNAME(), DATABASE_NAME() или @@DBNAME вернёт имя базы. И это, очевидно, не самое сложное и/или важное из того, что я забыл, никогда не знал, не умел. Ничего, как-то же справляюсь. Коллега что-то подзабыл, попросил "помощь клуба", вы подсказали и присутствующие восприняли ситуацию как нормальную? На собеседовании перед вами будущий коллега. Не забывайте об этом.

3. Давайте практические задания

Проверили знания — проверяйте практику. Это поможет реабилитироваться тем, кто неожиданно для себя на разговоре "поплыл", но фактически квалификацией обладает. А для менее опытных это скорее и есть главная часть собеседования. И под практикой, конечно же, имею в виду работу за компьютером в SSMS или аналоге. Написание запроса (или какую вы задачку придумали) с помощью клавиатуры, руками кандидата. Не на бумажке. И очень желательно адаптировать задание под тот уровень квалификации кандидата, который вы оценили в предыдущей части собеседования.

4. Не перегибайте с задачками

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

Заключение

Надеюсь, в статье не проглядывается поучительный тон и хочется верить, что удалось обнаружить интересные задачки. По каждому озвученному направлению, каждому кейсу, можно найти отдельные, иногда по-настоящему сложные материалы, статьи авторства MVP (статус Most Valuable Professional по результатам сертификации Microsoft раздаёт далеко не всем). А это всё-таки поверхностный обзор и самая что ни на есть базовая база.

Если узнали в статье много нового, то рекомендую обратиться к литературе.

Когда-то совсем давно, будучи студентом, купил книжку по SQL. Что ли для курсовой она мне понадобилась. Помню, что нужно было "получше разобраться с SQL". С вопросами из рубрики "как написать такой селект". Была она в мягком переплёте с зелёной обложкой. Через пару лет устроился на работу, там использовался MS SQL, столкнулся с трудностями и полез в эту книгу... внезапно обнаружил, что разделы, которые раньше были мне не нужны теперь пригодились, и книга оказалась не просто "по SQL", а именно по MS SQL Server 2000. Как называется, не помню, но она мне очень помогла. И понравился стиль изложения: автор разъяснял смыслы, а не цитировал документацию "от А до Я". Той книжки сейчас под рукой нет, но гугление нашло ISBN 978-5-388-00300-3 (найденную ссылку не добавляю из-за правил) и визуально она напоминает купленную в начале нулевых. Напишите, может, кто понял, о какой книжке речь. Если автор не забросил это дело, то можно бы порекомендовать современную версию книги.

Ещё у меня (тоже не под рукой) где-то лежат официальные майкрософтовские книжки издательства "Питер" (синие такие), та же "Implementation and maintenance". Книги той серии исчерпывающе покрывают тему, но формат и стиль мне совершенно не близки и не понравились — это как раз формат распечатанной документации, лекций "от А до Я". Сомневаюсь, что такая литература способна помочь в формировании системного понимания инструмента и его особенностей, хоть и не бесполезна. Любая из таких книжек в два — три раза толще той "зелёной".

Насчёт систематизации знаний смею порекомендовать преподавателя. Правда, было это больше десяти лет назад, позднее с ним не пересекался, но дважды рекомендовал коллегам. Каждый из них потом отзывался положительно. Это — Фёдор Самородов. Когда-то давно он преподавал в центре "Специалист", на сегодня никак сориентировать не смогу. Вроде бы коллеги упоминали, что на ютуб выложены некоторые его лекции (не проверял). Если знаете преподавателей, которых имеет смысл упомянуть вслух, то, пожалуйста, поделитесь в комментариях.

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

Много лет был подписан на классную рассылку "MS SQL Server - дело тонкое" (если не путаю название), где-то даже лежит архив этих писем. Но в рассылке сложно отмотать назад, этим она не очень удобна. Да и не знаю, насколько подобные механики обмена информацией актуальны в современном мире.

Но в целом, если вы активно гуглите тематические вопросы, то сами на достойное наткнётесь.

Главное — быть честным с самим собой, понять, что зубрёжкой грамотного собеседователя не преодолеть. И что базовый синтаксис языка SQL чрезвычайно примитивен. В условном IT-департаменте селект с несколькими джойнами и групбаем написать способен кто угодно. Такое умеют бизнес-аналитики, специалисты второй линии поддержки, бэкендеры, начальники управлений. Базовое селектописательство как профессию продать будет непросто. Так что, либо лютейшее селектописательство и куда-то в сторону отчётов и аналитики, либо админство (но там, уверяю, нелегко), либо копать в особенности работы СУБД и набивать руку правильными подходами, типовыми ошибками, развиваться как разработчику. Некоторый спрос на разработчиков БД, конечно, есть, но своеобразный. У MS SQL Server есть бесплатные конкуренты, с БД многие работают через ORM и "программистов БД" у них попросту нет. Хотя в подобном ключе, наверное, о любой профессии можно порассуждать. Что точно не увеличит шансы на получение работы, так это количество подписок на "телеграм-каналы".

И книги, и клавиатура — всё в ваших руках.

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 20: ↑20 and ↓0+20
Comments10

Articles