第 28.1 节:行级锁定
如果表使用 InnoDB,MySQL 会自动使用行级锁定,以便多个事务可以同时对同一个表进行读写,而不会相互等待。
如果两个事务试图修改同一行并且都使用行级锁定,其中一个事务会等待另一个事务完成。
还可以通过 SELECT ... FOR UPDATE 语句为每个预期要修改的行获取行级锁定。
考虑两个连接来详细解释行级锁定:
连接 1
START TRANSACTION;
SELECT ledgerAmount FROM accDetails WHERE id = 1 FOR UPDATE;
在连接 1 中,通过 SELECT ... FOR UPDATE 语句获取了行级锁。
连接 2
UPDATE accDetails SET ledgerAmount = ledgerAmount + 500 WHERE id = 1;
当有人在连接 2 中尝试更新同一行时,该更新将等待连接 1 完成事务,否则会根据 innodb_lock_wait_timeout 设置(默认 50 秒)显示错误消息:
Error Code: 1205. Lock wait timeout exceeded; try restarting transaction
要查看此锁的详细信息,请运行 SHOW ENGINE INNODB STATUS:
TRANSACTION 1973004, ACTIVE 7 sec updating
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 360, 1 row lock(s)
MySQL thread id 4, OS thread handle 0x7f996beac700, query id 30 localhost root update
UPDATE accDetails SET ledgerAmount = ledgerAmount + 500 WHERE id=1
TRX HAS BEEN WAITING 7 SEC FOR THIS LOCK TO BE GRANTED:
连接 2(更新另一行)
UPDATE accDetails SET ledgerAmount = ledgerAmount + 250 WHERE id = 2;
结果:1 row(s) affected —— 更新其他行不会出错。
连接 1(提交事务)
UPDATE accDetails SET ledgerAmount = ledgerAmount + 750 WHERE id = 1;
COMMIT;
结果:1 row(s) affected。事务提交后,行锁被释放。
连接 2(再次更新 id=1)
UPDATE accDetails SET ledgerAmount = ledgerAmount + 500 WHERE id = 1;
结果:1 row(s) affected —— 连接 1 释放行锁后,连接 2 的更新成功执行。
第 28.2 节:MySQL 锁
表锁对于 ENGINE=MyISAM 来说是一个重要的工具,但对于 ENGINE=InnoDB 来说几乎没有用处。如果你在 InnoDB 中使用表锁,你应该重新思考你的事务处理方式。
MySQL 允许客户端会话显式地获取表锁,以便与其它会话协作访问表,或者防止其它会话在某个会话需要独占访问表的期间修改表。一个会话只能为自己获取或释放锁。一个会话不能为另一个会话获取锁,也不能释放另一个会话持有的锁。
锁可用于模拟事务或加快更新表的速度。本节后面会更详细地解释这一点。
命令:
LOCK TABLES table_name READ|WRITE;
一次只能为单个表分配一种锁类型。
示例(读锁):
LOCK TABLES table_name READ;
示例(写锁):
LOCK TABLES table_name WRITE;
要查看是否应用了锁,请使用以下命令:
SHOW OPEN TABLES;
要刷新/移除所有锁,请使用以下命令:
UNLOCK TABLES;
示例(写锁):
LOCK TABLES products WRITE;
INSERT INTO products (id, product_name) SELECT id, old_product_name FROM old_products;
UNLOCK TABLES;
上面的示例中,在解锁 products 表之前,任何外部连接都不能向 products 表写入数据。
示例(读锁):
LOCK TABLES products READ;
INSERT INTO products (id, product_name) SELECT id, old_product_name FROM old_products;
UNLOCK TABLES;
上面的示例中,在解锁 products 表之前,任何外部连接都不能从 products 表读取数据。