第 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 表读取数据。