MySQL数据库中的锁是控制并发访问的重要机制,它们确保数据的一致性和完整性。下面是MySQL数据库中常见的锁类型以及它们的特点和应用场景:
SELECT
语句。INSERT
、UPDATE
、DELETE
语句。SELECT ... WHERE ... BETWEEN ... AND ...
语句。现在,让我们来看一个实际案例,说明MySQL中锁的应用:
假设有一个电子商务网站,多个用户同时访问产品库存信息,并尝试下单购买同一件商品,这时候可能会涉及到对产品库存的读写操作。
如果没有使用合适的锁机制,可能会导致以下问题:
CREATE TABLE articles (
article_id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
content TEXT
);
事务1:查看博客文章内容(共享锁)
共享锁允许多个事务同时读取同一资源,但阻止其他事务获取排他锁,从而防止并发写入。
START TRANSACTION;
SELECT * FROM articles WHERE article_id = 123 LOCK IN SHARE MODE;
-- 读取文章内容
COMMIT;
事务2:可以编辑文章内容(排他锁)
排他锁阻塞其他事务的读锁和写锁,只允许一个事务对资源进行写操作,防止其他事务读取或写入资源
START TRANSACTION;
SELECT * FROM articles WHERE article_id = 123 FOR UPDATE;
-- 编辑文章内容
UPDATE articles SET content = 'New content' WHERE article_id = 123;
COMMIT;
假设有一个在线预订系统,有一个订单表,需要定期对订单表进行清理和维护操作
CREATE TABLE orders (
order_id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT,
order_date DATETIME
);
-- 事务1:清理过期订单(表锁)
START TRANSACTION;
LOCK TABLES orders WRITE;
-- 清理过期订单
DELETE FROM orders WHERE order_date < DATE_SUB(NOW(), INTERVAL 30 DAY);
UNLOCK TABLES;
COMMIT;
假设有一个在线论坛系统,用户可以对帖子进行评论,需要确保在用户对同一篇帖子进行评论时不会产生冲突。
CREATE TABLE posts (
post_id INT AUTO_INCREMENT PRIMARY KEY,
title VARCHAR(255),
content TEXT
);
CREATE TABLE comments (
comment_id INT AUTO_INCREMENT PRIMARY KEY,
post_id INT,
user_id INT,
comment_text TEXT,
);
-- 事务1:添加评论(行锁)
START TRANSACTION;
SELECT * FROM posts WHERE post_id = 123 FOR UPDATE;
-- 添加评论
INSERT INTO comments (post_id, user_id, comment_text) VALUES (123, 456, 'Great post!');
COMMIT;
事务执行SELECT * FROM posts WHERE post_id = 123 FOR UPDATE;
语句获取了帖子ID为123的行级排他锁。这样做的目的是确保在添加评论之前,对于帖子ID为123的行数据,其他事务无法同时修改,以避免数据不一致或丢失的情况发生
假设有一个在线购物系统,其中有两张表:orders
(订单表)和order_details
(订单详情表)。订单表记录了每个订单的基本信息,订单详情表则记录了每个订单中的具体商品信息。
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT,
order_date DATETIME
);
CREATE TABLE order_details (
id INT AUTO_INCREMENT PRIMARY KEY,
order_id INT,
product_id INT,
quantity INT,
);
现在假设有一个事务需要向订单表中插入一条新的订单记录,并且需要向订单详情表中插入相关的商品信息。
START TRANSACTION;
-- 获取意向排他锁,表明将在订单表上进行排他锁定
SELECT * FROM orders WHERE customer_id = 100 FOR UPDATE;
-- 插入新的订单记录
INSERT INTO orders (customer_id, order_date) VALUES (100, NOW());
SET @last_order_id = LAST_INSERT_ID();
-- 插入订单详情
INSERT INTO order_details (order_id, product_id, quantity) VALUES (@last_order_id, 1, 2);
INSERT INTO order_details (order_id, product_id, quantity) VALUES (@last_order_id, 2, 3);
COMMIT;
初始:
执行事务后:
在这个例子中,事务首先获取了意向排他锁,以表明将在订单表上进行排他锁定。然后向订单表中插入了一条新的订单记录,并且通过获取LAST_INSERT_ID()
函数获取了刚插入的订单ID,接着向订单详情表中插入了相关的商品信息。
假设有一个产品表,其中的产品ID是一个唯一索引。现在有两个事务,一个事务需要检查某个特定的产品是否存在,而另一个事务需要向产品表中插入一个新的产品。
-- 事务1:检查产品是否存在
START TRANSACTION;
SELECT * FROM products WHERE product_id = 5 LOCK IN SHARE MODE;
-- 如果存在特定产品,则执行相应操作
COMMIT;
-- 事务2:插入新的产品
START TRANSACTION;
-- 获取间隙锁,防止其他事务在该范围内插入新的产品
SELECT * FROM products WHERE product_id > 4 AND product_id < 6 FOR UPDATE;
-- 插入新的产品
INSERT INTO products (product_id, name, price) VALUES (5, 'New Product', 10.99);
COMMIT;
事务1:检查产品是否存在
START TRANSACTION;
SELECT * FROM products WHERE product_id = 5 LOCK IN SHARE MODE;
-- 如果存在特定产品,则执行相应操作
INSERT INTO products (product_id, name, price) VALUES (5, 'New Product', 23.11);
COMMIT;
无法插入
在这个事务中,我们使用LOCK IN SHARE MODE
语句获取了产品ID为5的共享锁,以确保其他事务可以同时读取相同的产品信息,但是不能进行写操作。
事务2:插入新的产品
START TRANSACTION;
-- 获取间隙锁,防止其他事务在该范围内插入新的产品
SELECT * FROM products WHERE product_id > 4 AND product_id < 6 FOR UPDATE;
-- 插入新的产品
INSERT INTO products (product_id, name, price) VALUES (5, 'New Product', 10.99);
COMMIT;
无法插入
在这个事务中,我们使用FOR UPDATE
语句获取了产品ID大于4且小于6的间隙锁,以防止其他事务在这个范围内插入新的产品。然后,我们向产品表中插入了一个新的产品。
在MySQL中,锁升级是指事务在执行过程中,将当前持有的锁从低级别升级到更高级别的过程。这个过程通常是为了保证数据的完整性和一致性,同时尽量减少锁的持有时间,以提高并发性能。
MySQL中的锁升级有时是隐式的,有时需要显式操作。以下是一些锁升级的例子,以及相应的SQL语句:
CREATE TABLE products (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255),
category_id INT,
price DECIMAL(10, 2),
quantity INT
);
CREATE TABLE orders (
id INT AUTO_INCREMENT PRIMARY KEY,
customer_id INT,
product_id INT,
quantity INT,
order_date DATETIME,
);
从行级锁升级到表级锁:
例子:
START TRANSACTION;
SELECT * FROM products WHERE category_id = 1 FOR UPDATE;
-- 此时事务持有了行级锁
-- 执行一些操作,需要将行级锁升级到表级锁
UPDATE products SET price = price * 0.9 WHERE category_id = 1;
COMMIT;
在这个例子中,事务开始时使用SELECT ... FOR UPDATE
语句获取了category_id
为1的产品行的排他锁,随后执行更新操作需要将行级锁升级为表级锁。
从共享锁升级到排他锁:
例子:
START TRANSACTION;
SELECT * FROM products WHERE category_id = 1 LOCK IN SHARE MODE;
-- 此时事务持有了共享锁
-- 执行一些操作,需要将共享锁升级到排他锁
UPDATE products SET quantity = quantity - 1 WHERE category_id = 1;
COMMIT;
在这个例子中,事务开始时使用LOCK IN SHARE MODE
语句获取了category_id
为1的产品行的共享锁,但在后续执行中需要对该行进行修改,因此需要将共享锁升级为排他锁。
从意向锁升级到表级锁:
例子:
START TRANSACTION;
SELECT * FROM orders WHERE customer_id = 100 FOR UPDATE;
-- 此时事务持有了意向排他锁
-- 执行一些操作,需要将意向锁升级为表级锁
DELETE FROM orders WHERE customer_id = 100;
COMMIT;
在这个例子中,事务开始时使用SELECT ... FOR UPDATE
语句获取了customer_id
为100的订单行的意向排他锁,后续执行了删除操作,需要将意向锁升级为表级锁。