Строгий режим MySQL и почему он должен быть включен

April 6, 2011, revised June 30, 2012 ActiveRecord InnoDB MyISAM MySQL Ruby Ruby on Rails

В MySQL есть такой специальный режим, предназначенный для введения в базу неправильных данных. Например, чтобы вместо 20000000000 вставлять в INT-поле 2147483647. Или наполнять базу несуществующими датами. Или обрезанными строками. Ну или мало ли для чего этот режим может тебе пригодится.

Режим этот называется “обычный режим”. W. T. F?

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

Движок MyISAM, бывший стандартным движком MySQL до совсем недавних пор, не поддерживает транзакции. Если отваливался один из “как бы атомарного” набора запросов, база (в общем случае) теряла целостность. По сравнению с такой опасностью запись в базу неверных значений оказалась наименьшим злом.

Но ведь все сколько-нибудь компетентные вебдевелоперы знают, что надо использовать движок InnoDB, который – в контексте хранилища для сайта – со всех сторон лучше MyISAMа. В частности, InnoDB поддерживает транзакции, и все сколько-нибудь адекватные ORM (или их живые заместители, кодирующие запросы к базе) используют эти транзакции для обеспечения атомарности изменений.

Вот и получается, если ты используешь InnoDB и транзакции, щадящее отношение MySQL тебе только вредит. Каждая пропущенная проверка грозит засорением базы данных. Вот наглядный пример – каким полем ты хранишь URL? VARCHAR(255)? А адрес может быть и в 2 килобайта длиной. В “обычном режиме” при переполнении строки MySQL всего лишь запишет предупреждение в лог, который никто не читает.

Строгий режим

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

Говоря языком запросов,

SET sql_mode='STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE';

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

UPDATE 2012-06-30: В Rails 4 режим STRICT будет включен по умолчанию, и отключаться флагом в database.yml. Лучше поздно, чем никогда.

Чтобы всегда использовать этот режим в Ruby on Rails 3 (я не понимаю, почему он не включается автоматически), достаточно положить такой код куда-нибудь в инициализаторы:

class ActiveRecord::ConnectionAdapters::Mysql2Adapter

private
  alias_method :configure_connection_without_strict_mode, :configure_connection

  def configure_connection
    configure_connection_without_strict_mode
    execute "SET sql_mode='STRICT_TRANS_TABLES,NO_ZERO_DATE,NO_ZERO_IN_DATE'"
  end
end

О чем это я?

Вчера я долго искал, почему в тестах не работал плагин bounces-handler. Это происходило потому, что Rails не поддерживают тип INT UNSIGNED, который использовался плагином для хранения CRC32. И не просто не поддерживают, а записывают вместо него в схему знаковый INT. Поскольку rake db:test:prepare загружает именно схему, то в тестовом окружении контрольные суммы не влезали в рамки знакового поля и в базе оказывался мусор.

Это было последней каплей.

Мораль 1: не используй INT UNSIGNED с Rails

Пока не закрыт вот этот тикет.

Мораль 2: у всех полей должна быть валидация

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

Buy Me a Coffee at ko-fi.com