面试题答案
一键面试行锁和表锁互斥导致性能瓶颈的情况
- 高并发更新同一行数据:在电商系统商品库存扣减场景中,如果大量并发请求同时操作同一商品(即同一行数据),行锁会频繁竞争。例如多个用户同时抢购同一款热门商品,每个请求都要获取该行的行锁进行库存扣减,这会导致大量线程等待锁资源,从而形成性能瓶颈。
- 全表扫描操作与行锁冲突:当有SQL语句进行全表扫描(如没有合适索引的查询)并试图修改数据时,会获取表锁。例如,在
products
表中,若有一条无索引的UPDATE products SET some_column = value
语句执行,同时又有其他线程尝试对某几行商品数据进行库存扣减(行锁操作),表锁和行锁的互斥会使行锁操作等待表锁释放,造成性能下降。
通过事务隔离级别减少影响
- 读未提交(Read Uncommitted):此级别下事务可以读取其他事务未提交的数据,虽然能减少锁等待时间,但可能出现脏读问题,在电商库存扣减场景中不适用,因为可能读取到未确认的库存变更,导致数据不一致。
- 读已提交(Read Committed):这是MySQL默认级别,事务只能读取已提交的数据。在库存扣减场景中,使用此级别可以保证每次读取的库存数据是已确认的。但在高并发下,仍可能出现不可重复读问题,即同一事务内多次读取库存数据不一致。
- 可重复读(Repeatable Read):该级别下,在一个事务内多次读取同一数据时,数据保持一致,解决了不可重复读问题。在电商库存扣减场景中,开启此级别事务,一个事务在扣减库存过程中,不会受到其他事务库存变更影响,避免了数据不一致,同时减少了锁竞争导致的性能问题,因为减少了不必要的锁等待。例如:
START TRANSACTION;
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT stock FROM products WHERE product_id = 1 FOR UPDATE;
-- 这里进行库存扣减逻辑
UPDATE products SET stock = stock - 1 WHERE product_id = 1;
COMMIT;
- 串行化(Serializable):此级别最高,会对所有读取操作加锁,防止并发事务干扰,但会极大增加锁竞争,在高并发电商库存扣减场景下性能极低,一般不使用。
通过优化SQL语句减少影响
- 添加合适索引:在
products
表的product_id
字段上添加索引,可避免全表扫描。例如:
CREATE INDEX idx_product_id ON products(product_id);
这样在进行库存扣减操作UPDATE products SET stock = stock - 1 WHERE product_id = 1
时,MySQL能快速定位到需要操作的行,减少获取锁的时间,降低锁竞争。
2. 批量操作:如果是多个商品库存扣减,可以将多个操作合并为一个批量操作。例如,原本需要多次执行UPDATE products SET stock = stock - 1 WHERE product_id = x
,可以改为:
START TRANSACTION;
UPDATE products SET stock = CASE
WHEN product_id = 1 THEN stock - 1
WHEN product_id = 2 THEN stock - 2
-- 其他商品扣减逻辑
END
WHERE product_id IN (1, 2, ...);
COMMIT;
这样只需要获取一次锁,减少锁竞争,提高系统性能。