Данная статья посвящена блокировке страниц в MySQL. Она основана на официальной документации.
LOCK TABLES and UNLOCK TABLES
Синтаксис блокировки, разблокировки таблиц
LOCK TABLES tbl_name [[AS] alias] lock_type [, tbl_name [[AS] alias] lock_type] … lock_type: READ [LOCAL] | [LOW_PRIORITY] WRITE UNLOCK TABLES |
MySQL позволяет сессиям клиентов явно блокировать таблицы. Сессия может блокировать таблицы только для себя. Блокировка может быть использована, чтобы эмулировать транзакции, чтобы получить больше скорости при обновлении таблиц. Для блокировки таблиц должны быть соответствующие права (priveleges), а также SELECT privilege для каждого заблокированного объекта.
Типы блокировок
READ [LOCAL] lock: (блокировка чтения)
Сессия, которая блокирует таблицу может только читать её, но не писать в неё.
Несколько сессий могут заблокировать одну таблицу для чтения в одно и тоже время.
[LOW_PRIORITY] WRITE lock: (блокировка записи)
Сессия, которая блокирует таблицу может читать и писать в таблицу.
Только сессия, которая держит блокировку имеет доступ к таблице до тех пор пока блокировка не будет снята.
Попытки других сессий заблокировать таблицу если она уже заблокирована отвергаются.
Сессия, которая требует блокировки должна требовать блокировку всех таблиц, которые ей нужны в одном утверждении LOCK TABLES. При этом сессия может обратиться только к заблокированным таблицам. В примере ниже происходит ошибка, когда сессия пытается получить доступ к таблице t2, потому как она не была заблокирована в инструкции LOCK TABLES.
mysql> LOCK TABLES t1 READ; mysql> SELECT COUNT(*) FROM t1; +———-+ | COUNT(*) | +———-+ | 3 | +———-+ mysql> SELECT COUNT(*) FROM t2; ERROR 1100 (HY000): Table ‘t2’ was not locked with LOCK TABLES |
К таблицам из INFORMATION_SCHEMA невозможно обратиться, заблокировав их явно. Вы не можете ссылаться на заблокированные таблицы несколько раз в одном запросе, используя одно имя. Вместо этого используйте псевдонимы (aliases), чтобы получить отдельную блокировку для таблицы в каждом псевдониме (alias):
mysql> LOCK TABLE t WRITE, t AS t1 READ; mysql> INSERT INTO t SELECT * FROM t; ERROR 1100: Table ‘t’ was not locked with LOCK TABLES mysql> INSERT INTO t SELECT * FROM t AS t1; |
Если утверждение ссылается на таблицу по значению псевдонима, необходимо блокировать таблицу, используя тот же самый псевдоним.
mysql> LOCK TABLE t READ; mysql> SELECT * FROM t AS myalias; ERROR 1100: Table ‘myalias’ was not locked with LOCK TABLES |
И напротив, если вы блокируете таблицу, используя псевдоним, вы должны ссылаться на неё, используя этот псевдоним.
mysql> LOCK TABLE t AS myalias READ; mysql> SELECT * FROM t; ERROR 1100: Table ‘t’ was not locked with LOCK TABLES mysql> SELECT * FROM t AS myalias; |
WRITE имеет больший приоритет чем READ, это значит, что если одна сессия блокирует с параметром READ, а другая в это же время блокирует с параметром WRITE, то первая сессия будет ждать, пока не закончит вторая.
Это поведение можно переписать, используя параметр LOW_PRIORITY WRITE (более подробно в документации).
Правила UNLOCK
Таблицы могут быть разблокированы явно или неявно при выполнении некоторых условий
-Явная разблокировка при использовании UNLOCK TABLES
-Если сессия пытается заблокировать таблицы повторно, то первая блокировка снимается и ставится вторая
-Если сессия начинает транзакцию (START TRANSACTION) на заблокированных таблицах, то блокировка снимается
Взаимодействие Table Locking и Транзакций
LOCK TABLES и UNLOCK TABLES взаимодействуют следующим образом.
LOCK TABLES это не безопасный с точки зрения транзакций способ. Если была какая-то транзакция и вызван метод LOCK TABLES, то эта транзакция коммитится. Таким образом LOCK TABLES неявно подтверждает любые активные действия до применения блокировки таблиц.
UNLOCK TABLES неявно подтверждает все активные транзакции, но только если была использована инструкция LOCK TABLES. В следующем примере UNLOCK TABLES закрывает все таблицы, но не подтверждает транзакцию, потому что никакая таблица не заблокирована
FLUSH TABLES WITH READ LOCK; START TRANSACTION; SELECT … ; UNLOCK TABLES; |
Начало транзакции, например START TRANSACTION, неявно записывает все изменения в базу и снимает блокировку таблиц.
Для справки. Инструкция FLUSH TABLES WITH READ LOCK;
Закрываются все открытые таблицы и блокируется доступ для чтения всех таблиц для всех баз данных, пока не будет запущена команда UNLOCK TABLES
. Это очень удобный способ создавать резервные копии, если у вас файловая система наподобие Veritas, которая может обеспечить моментальные снимки данных в режиме реального времени.
FLUSH TABLES WITH READ LOCK это не тоже самое, что LOCK TABLES и UNLOCK TABLES, то есть, например START TRANSACTION не отменит global read lock
Корректный путь для использования LOCK TABLES и UNLOCKTABLES с транзакционными таблицами, такими как InnoDB, это начать с инструкции SET autocommit = 0 (а не с инструкции START TRANSACTION), далее LOCK TABLES, далее что-то делаем с заблокированными таблицами, далее COMMIT и только после этого UNLOCK TABLES, например можно это сделать так…
SET autocommit=0; LOCK TABLES t1 WRITE, t2 READ, ...; ... do something with tables t1 and t2 here ... COMMIT; UNLOCK TABLES; |
Это корректный код для взаимодействия сервера MySQL и движка InnoDB. По умолчанию для InnoDB стоит autocommit=1;
ROLLBACK не освобождает транзакции.
Блокировка таблиц и триггеры
Если заблокировать явно с использованием LOCK TABLES, то любые таблицы, используемые в триггерах также неявно блокируются (что логично).
Если в триггере таблица используется только для чтения, то она и блокируется только на чтение.
Аналогично, если в триггере таблица используется на запись, то она и блокируется на запись.
Если таблица заблокирована явно на чтение LOCK TABLES
Предположим, мы заблокировали 2 таблицы
LOCK TABLES t1 WRITE, t2 READ; |
Предположим таблица t1 имеет триггер
CREATE TRIGGER t1_a_ins AFTER INSERT ON t1 FOR EACH ROW BEGIN UPDATE t4 SET count = count+1 WHERE id = NEW.id AND EXISTS (SELECT a FROM t3); INSERT INTO t2 VALUES(1, 2); END; |
Результат блокировки таблиц (LOCK TABLES) в том, что t1 и t2 блокируются благодаря первому утверждению, а t3 и t4 блокируются, потому что они в триггере.
t1 блокируется для записи
t2 блокируется для записи, не смотря на то, что она изначально была заблокирована для чтения, потому что в триггере для этой таблицы используется инструкция INSERT
t3 блокируется для чтения, потому что она читается в триггере
t4 блокируется для записи, потому что она может быть обновлена в триггере
Ограничения и условия блокировки таблиц
Можно безопасно уничтожать сессию, которая ожидает блокировку таблиц, используя синтаксис KILL.
Не следует блокировать таблицы, которые Вы используете с INSERT DELAYED, так как это приведет к ошибке.
LOCK TABLES и UNLOCK TABLES не могут быть использованы внутри хранимой процедуры.
Таблице в БД performance_schema не могут быть заблокированы с LOCK TABLES, кроме setup_xxx таблиц.
I’m trying to import a mysqldump
backup of a drupal
database taken from a server running MySQL 5.0
(which will be upgraded to 5.5
) onto my local machine running MySQL 5.5.23
. The import fails with the below error:
ERROR 1100 (HY000) at line 11084: Table 'search_index' was not locked with LOCK TABLES
When I open up the backup at line 11084
here is what I get:
LOCK TABLES `sessions` WRITE;
/*!40000 ALTER TABLE `sessions` DISABLE KEYS */;
INSERT INTO `sessions` VALUES ...
-- MySQL dump 10.11
--
-- Host: localhost Database: drupal
-- ------------------------------------------------------
-- Server version 5.0.77-log
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `search_index`
--
DROP TABLE IF EXISTS `search_index`; <=== LINE 11084
SET @saved_cs_client = @@character_set_client;
SET character_set_client = utf8;
CREATE TABLE `search_index` (
`word` varchar(50) NOT NULL default '',
`sid` int(10) unsigned NOT NULL default '0',
`type` varchar(16) default NULL,
`score` float default NULL,
UNIQUE KEY `word_sid_type` (`word`,`sid`,`type`),
KEY `sid_type` (`sid`,`type`),
KEY `word` (`word`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
SET character_set_client = @saved_cs_client;
I checked the other statements and normally INSERT
statements are followed by UNLOCK TABLES;
. Accordingly if I make the below modifications to the mysqldump
file the problem is fixed:
INSERT INTO `sessions` VALUES ...
UNLOCK TABLES; <=== ADDED LINE
-- MySQL dump 10.11
[...]
DROP TABLE IF EXISTS `search_index`;
I don’t know what’s wrong there but the whole comment block starting with -- MySQL dump 10.11
is the same as the one at the beginning of the mysqldump
file, and it looks like mysqldump
started a new dump while writing the INSERT INTO sessions
statements which were not closed properly with UNLOCK TABLES;
.
Does someone have any idea as to what’s going on here?
Error: 1100 SQLSTATE: HY000
(ER_TABLE_NOT_LOCKED)
Message: Table ‘%s’ was not locked with LOCK TABLES
Error: 1101 SQLSTATE: 42000
(ER_BLOB_CANT_HAVE_DEFAULT)
Message: BLOB/TEXT column ‘%s’ can’t have a default value
Error: 1102 SQLSTATE: 42000
(ER_WRONG_DB_NAME)
Message: Incorrect database name ‘%s’
Error: 1103 SQLSTATE: 42000
(ER_WRONG_TABLE_NAME)
Message: Incorrect table name ‘%s’
Error: 1104 SQLSTATE: 42000
(ER_TOO_BIG_SELECT)
Message: The SELECT would examine more than MAX_JOIN_SIZE rows;
check your WHERE and use SET SQL_BIG_SELECTS=1 or SET
SQL_MAX_JOIN_SIZE=# if the SELECT is okay
Error: 1105 SQLSTATE: HY000
(ER_UNKNOWN_ERROR)
Message: Unknown error
Error: 1106 SQLSTATE: 42000
(ER_UNKNOWN_PROCEDURE)
Message: Unknown procedure ‘%s’
Error: 1107 SQLSTATE: 42000
(ER_WRONG_PARAMCOUNT_TO_PROCEDURE)
Message: Incorrect parameter count to procedure ‘%s’
Error: 1108 SQLSTATE: HY000
(ER_WRONG_PARAMETERS_TO_PROCEDURE)
Message: Incorrect parameters to procedure ‘%s’
Error: 1109 SQLSTATE: 42S02
(ER_UNKNOWN_TABLE)
Message: Unknown table ‘%s’ in %s
Error: 1110 SQLSTATE: 42000
(ER_FIELD_SPECIFIED_TWICE)
Message: Column ‘%s’ specified twice
Error: 1111 SQLSTATE: HY000
(ER_INVALID_GROUP_FUNC_USE)
Message: Invalid use of group function
Error: 1112 SQLSTATE: 42000
(ER_UNSUPPORTED_EXTENSION)
Message: Table ‘%s’ uses an extension that doesn’t exist in this
MySQL version
Error: 1113 SQLSTATE: 42000
(ER_TABLE_MUST_HAVE_COLUMNS)
Message: A table must have at least 1 column
Error: 1114 SQLSTATE: HY000
(ER_RECORD_FILE_FULL)
Message: The table ‘%s’ is full
Error: 1115 SQLSTATE: 42000
(ER_UNKNOWN_CHARACTER_SET)
Message: Unknown character set: ‘%s’
Error: 1116 SQLSTATE: HY000
(ER_TOO_MANY_TABLES)
Message: Too many tables; MySQL can only use %d tables in a join
Error: 1117 SQLSTATE: HY000
(ER_TOO_MANY_FIELDS)
Message: Too many columns
Error: 1118 SQLSTATE: 42000
(ER_TOO_BIG_ROWSIZE)
Message: Row size too large. The maximum row size for the used
table type, not counting BLOBs, is %ld. You have to change some
columns to TEXT or BLOBs
Error: 1119 SQLSTATE: HY000
(ER_STACK_OVERRUN)
Message: Thread stack overrun: Used: %ld of a %ld stack. Use
‘mysqld —thread_stack=#’ to specify a bigger stack if needed
Error: 1120 SQLSTATE: 42000
(ER_WRONG_OUTER_JOIN)
Message: Cross dependency found in OUTER JOIN; examine your ON
conditions
Error: 1121 SQLSTATE: 42000
(ER_NULL_COLUMN_IN_INDEX)
Message: Table handler doesn’t support NULL in given index. Please
change column ‘%s’ to be NOT NULL or use another handler
Error: 1122 SQLSTATE: HY000
(ER_CANT_FIND_UDF)
Message: Can’t load function ‘%s’
Error: 1123 SQLSTATE: HY000
(ER_CANT_INITIALIZE_UDF)
Message: Can’t initialize function ‘%s’; %s
Error: 1124 SQLSTATE: HY000
(ER_UDF_NO_PATHS)
Message: No paths allowed for shared library
Error: 1125 SQLSTATE: HY000
(ER_UDF_EXISTS)
Message: Function ‘%s’ already exists
Error: 1126 SQLSTATE: HY000
(ER_CANT_OPEN_LIBRARY)
Message: Can’t open shared library ‘%s’ (errno: %d %s)
Error: 1127 SQLSTATE: HY000
(ER_CANT_FIND_DL_ENTRY)
Message: Can’t find symbol ‘%s’ in library
Error: 1128 SQLSTATE: HY000
(ER_FUNCTION_NOT_DEFINED)
Message: Function ‘%s’ is not defined
Error: 1129 SQLSTATE: HY000
(ER_HOST_IS_BLOCKED)
Message: Host ‘%s’ is blocked because of many connection errors;
unblock with ‘mysqladmin flush-hosts’
Error: 1130 SQLSTATE: HY000
(ER_HOST_NOT_PRIVILEGED)
Message: Host ‘%s’ is not allowed to connect to this MySQL server
Error: 1131 SQLSTATE: 42000
(ER_PASSWORD_ANONYMOUS_USER)
Message: You are using MySQL as an anonymous user and anonymous
users are not allowed to change passwords
Error: 1132 SQLSTATE: 42000
(ER_PASSWORD_NOT_ALLOWED)
Message: You must have privileges to update tables in the mysql
database to be able to change passwords for others
Error: 1133 SQLSTATE: 42000
(ER_PASSWORD_NO_MATCH)
Message: Can’t find any matching row in the user table
Error: 1134 SQLSTATE: HY000
(ER_UPDATE_INFO)
Message: Rows matched: %ld Changed: %ld Warnings: %ld
Error: 1135 SQLSTATE: HY000
(ER_CANT_CREATE_THREAD)
Message: Can’t create a new thread (errno %d); if you are not out
of available memory, you can consult the manual for a possible
OS-dependent bug
Error: 1136 SQLSTATE: 21S01
(ER_WRONG_VALUE_COUNT_ON_ROW)
Message: Column count doesn’t match value count at row %ld
Error: 1137 SQLSTATE: HY000
(ER_CANT_REOPEN_TABLE)
Message: Can’t reopen table: ‘%s’
Error: 1138 SQLSTATE: 22004
(ER_INVALID_USE_OF_NULL)
Message: Invalid use of NULL value
Error: 1139 SQLSTATE: 42000
(ER_REGEXP_ERROR)
Message: Got error ‘%s’ from regexp
Error: 1140 SQLSTATE: 42000
(ER_MIX_OF_GROUP_FUNC_AND_FIELDS)
Message: Mixing of GROUP columns (MIN(),MAX(),COUNT(),…) with no
GROUP columns is illegal if there is no GROUP BY clause
Error: 1141 SQLSTATE: 42000
(ER_NONEXISTING_GRANT)
Message: There is no such grant defined for user ‘%s’ on host ‘%s’
Error: 1142 SQLSTATE: 42000
(ER_TABLEACCESS_DENIED_ERROR)
Message: %s command denied to user ‘%s’@’%s’ for table ‘%s’
Error: 1143 SQLSTATE: 42000
(ER_COLUMNACCESS_DENIED_ERROR)
Message: %s command denied to user ‘%s’@’%s’ for column ‘%s’ in
table ‘%s’
Error: 1144 SQLSTATE: 42000
(ER_ILLEGAL_GRANT_FOR_TABLE)
Message: Illegal GRANT/REVOKE command; please consult the manual
to see which privileges can be used
Error: 1145 SQLSTATE: 42000
(ER_GRANT_WRONG_HOST_OR_USER)
Message: The host or user argument to GRANT is too long
Error: 1146 SQLSTATE: 42S02
(ER_NO_SUCH_TABLE)
Message: Table ‘%s.%s’ doesn’t exist
Error: 1147 SQLSTATE: 42000
(ER_NONEXISTING_TABLE_GRANT)
Message: There is no such grant defined for user ‘%s’ on host ‘%s’
on table ‘%s’
Error: 1148 SQLSTATE: 42000
(ER_NOT_ALLOWED_COMMAND)
Message: The used command is not allowed with this MySQL version
Error: 1149 SQLSTATE: 42000
(ER_SYNTAX_ERROR)
Message: You have an error in your SQL syntax; check the manual
that corresponds to your MySQL server version for the right syntax
to use