面试题答案
一键面试1. 各类锁的工作原理
- 共享锁(S锁):
- 共享锁又称为读锁。若事务T对数据对象A加上S锁,那么其他事务只能对A再加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了多个事务可以同时读取数据,实现并发读操作。例如,多个用户同时查询某条商品信息时,都可以获取该商品数据行的S锁,从而并行读取数据。
- 排他锁(X锁):
- 排他锁又称写锁。若事务T对数据对象A加上X锁,其他任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。这确保了只有持有X锁的事务才能对数据进行修改,防止其他事务并发修改数据,避免数据不一致问题。比如,当一个事务要更新商品价格时,会先获取该商品数据行的X锁,其他事务就不能同时修改这个商品价格。
- 意向锁:
- 意向锁是为了在一个事务中表明下一个操作是意向共享锁(IS锁)还是意向排他锁(IX锁)。
- 意向共享锁(IS锁):事务打算给数据行加共享锁前要先获取该表的IS锁。例如,事务要对表中某几行数据加S锁,就先获取该表的IS锁。
- 意向排他锁(IX锁):事务打算给数据行加排他锁前要先获取该表的IX锁。比如,事务要对表中某行数据加X锁,就要先获取该表的IX锁。意向锁的作用是在表级别的锁和行级别的锁之间建立一种联系,避免直接在表级别加锁时,需要逐行判断是否有锁冲突,提高加锁效率。
2. 不同隔离级别下对并发操作性能的影响
- 读未提交(Read Uncommitted):
- 此隔离级别下,事务可以读取其他事务未提交的数据,几乎不使用锁机制(除了用于防止更新丢失的锁)。并发性能高,但可能出现脏读、不可重复读和幻读问题。例如,一个事务修改了数据但未提交,另一个事务可以读到这个未提交的数据,若第一个事务回滚,第二个事务读到的数据就是无效的。
- 读已提交(Read Committed):
- 在该隔离级别下,一个事务只能读取其他事务已提交的数据。使用共享锁在读取数据时,读完即释放,写操作使用排他锁。可避免脏读,但可能出现不可重复读和幻读。因为每次读取数据时共享锁都会重新获取,可能读到其他事务修改后的数据。并发性能相对较高,但比读未提交稍低。
- 可重复读(Repeatable Read):
- 事务在执行过程中,多次读取同一数据时,得到的结果是一致的。在读取数据时会获取共享锁,并且在事务结束前不会释放。写操作同样使用排他锁。此隔离级别可避免脏读和不可重复读,但在某些情况下仍可能出现幻读(MySQL InnoDB引擎通过MVCC机制解决了幻读问题)。由于锁的持有时间较长,并发性能相对读已提交有所降低。
- 串行化(Serializable):
- 这是最高的隔离级别,事务完全串行执行,通过对读取的数据加范围锁(防止幻读),写操作加排他锁。能避免所有并发问题,但并发性能极低,因为事务之间需要排队执行,几乎不存在并发操作。
3. 高并发场景下优化锁使用提升系统性能的示例
假设我们有一个电商系统中的商品库存表products
,有product_id
、stock
等字段。在高并发下单场景下,需要减少锁的争用,提升性能。
- 减少锁粒度:
- 传统方式可能对整个
products
表加锁,在高并发下会导致严重的性能问题。可以只对涉及的具体商品数据行加锁。例如:
- 传统方式可能对整个
-- 开启事务
START TRANSACTION;
-- 对指定商品行加排他锁
SELECT stock FROM products WHERE product_id = 1 FOR UPDATE;
-- 检查库存并更新库存
UPDATE products SET stock = stock - 1 WHERE product_id = 1 AND stock > 0;
-- 提交事务
COMMIT;
- 这样只锁住具体商品行,而不是整个表,减少了锁争用范围,提高并发性能。
- 使用乐观锁:
- 乐观锁假设在大多数情况下不会发生并发冲突,只有在更新数据时才检查数据是否被其他事务修改。在商品库存表中,可以增加一个版本号字段
version
。
- 乐观锁假设在大多数情况下不会发生并发冲突,只有在更新数据时才检查数据是否被其他事务修改。在商品库存表中,可以增加一个版本号字段
-- 开启事务
START TRANSACTION;
-- 获取当前商品库存和版本号
SELECT stock, version FROM products WHERE product_id = 1;
-- 模拟业务逻辑,计算需要更新的库存
SET @new_stock = @old_stock - 1;
-- 使用版本号更新库存,只有当版本号未改变时才更新成功
UPDATE products SET stock = @new_stock, version = version + 1 WHERE product_id = 1 AND version = @old_version;
-- 检查更新是否成功
SELECT ROW_COUNT();
-- 根据更新结果决定是否提交事务
IF ROW_COUNT() = 1 THEN
COMMIT;
ELSE
ROLLBACK;
END IF;
- 乐观锁减少了锁的持有时间,提高了并发性能,但需要注意更新失败时的处理,可能需要重试操作。
- 合理调整隔离级别:
- 根据业务场景,如果对数据一致性要求不是特别高,可将隔离级别从可重复读降低到读已提交。例如在一些允许少量不可重复读情况的统计页面,降低隔离级别可以减少锁的持有时间,提升并发性能。
-- 设置事务隔离级别为读已提交
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
- 但调整隔离级别时要充分评估对业务数据一致性的影响。