Как улучшить INSERT INTO ... SELECT блокировку поведения - PullRequest
37 голосов
/ 15 апреля 2010

В нашей производственной базе данных мы запускали следующий пакетный запрос SQL с псевдокодом, который выполняется каждый час:

INSERT INTO TemporaryTable
    (SELECT FROM HighlyContentiousTableInInnoDb
     WHERE allKindsOfComplexConditions are true)

Теперь сам запрос не обязательно должен быть быстрым, но я заметил, что он блокирует HighlyContentiousTableInInnoDb, хотя он только читает из него. Что заставляло некоторые другие очень простые запросы занимать ~ 25 секунд (именно столько времени занимает другой запрос).

Затем я обнаружил, что таблицы InnoDB в таком случае фактически блокируются с помощью SELECT! http://www.mysqlperformanceblog.com/2006/07/12/insert-into-select-performance-with-innodb-tables/

Но мне не очень нравится решение в статье выбора OUTFILE, оно выглядит как хак (временные файлы в файловой системе кажутся отстойными). Есть еще идеи? Есть ли способ сделать полную копию таблицы InnoDB, не блокируя ее таким образом во время копирования. Тогда я мог бы просто скопировать HighlyContentiousTable в другую таблицу и выполнить там запрос.

Ответы [ 9 ]

23 голосов
/ 28 октября 2010

Ответ на этот вопрос теперь намного проще: - Используйте уровень изоляции на основе репликации и чтения с фиксацией.

Блокировка, с которой вы столкнулись, исчезает.

Более подробное объяснение: http://harrison -fisk.blogspot.com / 2009/02 / my-favourite-new-feature-of-mysql-51.html

5 голосов
/ 21 января 2016

Вы можете установить формат binlog следующим образом:

SET GLOBAL binlog_format = 'ROW';

Отредактируйте my.cnf, если хотите сделать постоянным:

[mysqld]
binlog_format=ROW

Установить уровень изоляции для текущего сеанса перед выполнением запроса:

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
INSERT INTO t1 SELECT ....;

Если это не поможет, попробуйте установить уровень изоляции сервера в целом, а не только для текущего сеанса:

SET GLOBAL TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

Отредактируйте my.cnf, если хотите сделать постоянным:

[mysqld]
transaction-isolation = READ-UNCOMMITTED

Вы можете изменить READ-UNCOMMITTED на READ-COMMITTED, что является лучшим уровнем изоляции.

5 голосов
/ 19 января 2016

Каждый, кто использует таблицы Innodb, вероятно, привык к тому, что Innodb таблицы выполняют неблокирующее чтение, то есть, если вы не используете некоторые модификаторы, такие как LOCK IN SHARE MODE или FOR UPDATE, операторы SELECT не будет блокировать строки во время работы.

Как правило, это правильно, однако есть заметное исключение - INSERT INTO table1 SELECT * FROM table2. Этот оператор выполнит блокировку чтения (разделяемые блокировки) для таблицы table2. Это также относится к аналогичным таблицам с предложением where и объединениями. Это важно для таблиц, которые читаются как Innodb - даже если записи выполняются в таблице MyISAM.

Итак, почему это было сделано, будучи довольно плохим для производительности MySQL и параллелизм?

Причина - репликация. В MySQL до 5.1 репликация основана на операторах, что означает, что операторы, отвечающие на ведущее устройство, должны вызывать тот же эффект, что и на ведомом устройстве. Если бы Innodb не блокировал строки в исходной таблице, другая транзакция могла бы изменить строку и зафиксировать ее перед транзакцией, выполняющей инструкцию INSERT .. SELECT. Это приведет к тому, что эта транзакция будет применена к ведомому устройству перед оператором INSERT… SELECT и, возможно, приведет к получению данных, отличных от данных на ведущем устройстве. Блокировка строк в исходной таблице при их чтении защищает от этого эффекта, так как другие транзакции изменяют строки до того, как INSERT… SELECT сможет получить к ней доступ, также будут изменены в том же порядке на ведомом устройстве. Если транзакция пытается изменить строку после того, как к ней был получен доступ и она заблокирована INSERT… SELECT, транзакция должна будет дождаться завершения инструкции, чтобы убедиться, что она будет выполнена на ведомом устройстве в правильном порядке. Становится довольно сложным? Ну, все, что вам нужно знать, это нужно было сделать перед репликацией, чтобы работать в MySQL до 5.1.

В MySQL 5.1 это, а также некоторые другие проблемы должны решаться путем репликации на основе строк. Тем не менее, я еще не дал ему настоящие стресс-тесты, чтобы увидеть, насколько хорошо он работает:)

Еще одна вещь, которую следует учитывать - INSERT ... SELECT фактически выполняет чтение в режиме блокировки и, таким образом, частично обходит управление версиями и извлекает последнюю принятую строку. Таким образом, даже если вы работаете в режиме REPEATABLE-READ, эта операция будет выполняться в режиме READ-COMMITTED. режим, потенциально дающий другой результат по сравнению с тем, что даст чистый SELECT. Это, кстати, относится и к SELECT .. LOCK IN SHARE MODE и к SELECT ... FOR UPDATE.

Один мой вопрос: а что, если я не использую репликацию и мой двоичный журнал отключен? Если репликация не используется, вы можете включить опцию innodb_locks_unsafe_for_binlog, которая ослабит блокировки, которые Innodb устанавливает при выполнении оператора, что обычно обеспечивает лучший параллелизм. Однако, как следует из названия, блокировка небезопасна для репликации и восстановления во времени, поэтому используйте параметр innodb_locks_unsafe_for_binlog с осторожностью.

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

1 голос
/ 26 апреля 2010

Если вы можете разрешить некоторые аномалии, вы можете изменить УРОВЕНЬ ИЗОЛЯЦИИ на наименее строгий - ЧИТАТЬ НЕОГРАНИЧЕННЫЙ Но в течение этого времени кому-то разрешено читать из таблицы назначения. Или вы можете заблокировать таблицу назначения вручную (я полагаю, что mysql предоставляет эту функцию?).

Или же вы можете использовать READ COMMITTED, который также не должен блокировать исходную таблицу. Но он также блокирует вставленные строки в целевой таблице до фиксации.

Я бы выбрал второй.

1 голос
/ 24 апреля 2010

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

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

Потенциальные проблемы:

  • 2 х сохраненных данных
  • Дополнительная работа для всех вставок, обновлений и удалений
  • Может не быть транзакционным
1 голос
/ 15 апреля 2010

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

Create View temp as SELECT FROM HighlyContentiousTableInInnoDb WHERE allKindsOfComplexConditions are true

После этого вы можете использовать оператор вставки в этом представлении. Как то так

INSERT INTO TemporaryTable (SELECT * FROM temp)

Это только мое предложение.

0 голосов
/ 16 марта 2016

Я столкнулся с той же проблемой, используя CREATE TEMPORARY TABLE ... SELECT ... с SQLSTATE[HY000]: General error: 1205 Lock wait timeout exceeded; try restarting transaction.

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

LOCK TABLES HighlyContentiousTableInInnoDb READ;
INSERT INTO TemporaryTable
    (SELECT FROM HighlyContentiousTableInInnoDb
    WHERE allKindsOfComplexConditions are true)
UNLOCK TABLES;
0 голосов
/ 26 апреля 2010

Я не знаком с MySQL, но, надеюсь, в SQL Server есть эквивалент уровней изоляции транзакций Snapshot и Read committed snapshot. Использование любого из них должно решить вашу проблему.

0 голосов
/ 24 апреля 2010

Причиной блокировки (readlock) является защита транзакции чтения от чтения «грязных» данных, которые в данный момент может записывать параллельная транзакция. Большинство СУБД предлагают настройки, которые пользователи могут устанавливать и отменять блокировки чтения и записи вручную. Это может быть интересно для вас, если чтение грязных данных не является проблемой в вашем случае.

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

Но вот мозговой штурм: если пространство не имеет значения, вы можете подумать о запуске двух экземпляров одной и той же таблицы. HighlyContentiousTableInInnoDb2 для вашей транзакции постоянного чтения / записи и HighlyContentiousTableInInnoDb2_shadow для вашего пакетного доступа. Возможно, вы можете заполнить теневую таблицу автоматически с помощью триггера / подпрограмм внутри вашей СУБД, что быстрее и эффективнее, чем дополнительная транзакция записи везде.

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

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