面试题答案
一键面试锁模式调整
- 细化锁粒度
- 方法:尽量使用行级锁而非表级锁。例如,在订单创建时,只锁定涉及的具体订单行记录,而不是整个订单表。对于库存更新,锁定具体的库存行记录。在MySQL的InnoDB存储引擎中,默认对于符合索引条件的查询会使用行级锁。如
UPDATE products SET stock = stock - 1 WHERE product_id = 123 AND stock > 0;
,如果product_id
字段有索引,就会使用行级锁。 - 难点:需要确保SQL语句使用合适的索引,否则可能会退化为表级锁。应对措施是定期进行SQL性能分析,使用
EXPLAIN
关键字查看执行计划,确保索引正确使用。
- 方法:尽量使用行级锁而非表级锁。例如,在订单创建时,只锁定涉及的具体订单行记录,而不是整个订单表。对于库存更新,锁定具体的库存行记录。在MySQL的InnoDB存储引擎中,默认对于符合索引条件的查询会使用行级锁。如
- 合理选择锁类型
- 方法:对于读多写少的场景,可使用共享锁(S锁)和排他锁(X锁)的合理组合。例如,在查询库存信息时使用共享锁,多个事务可以同时读取库存,提高并发读性能。在更新库存时使用排他锁,保证数据一致性。如
SELECT stock FROM products WHERE product_id = 123 LOCK IN SHARE MODE;
获取共享锁,UPDATE products SET stock = stock - 1 WHERE product_id = 123 FOR UPDATE;
获取排他锁。 - 难点:可能会出现死锁情况,例如两个事务分别持有不同资源的锁并试图获取对方持有的锁。应对措施是设置合理的事务等待超时时间,MySQL默认有innodb_lock_wait_timeout参数,可根据业务场景适当调整。同时,使用死锁检测机制,InnoDB引擎会自动检测死锁并回滚其中一个事务。
- 方法:对于读多写少的场景,可使用共享锁(S锁)和排他锁(X锁)的合理组合。例如,在查询库存信息时使用共享锁,多个事务可以同时读取库存,提高并发读性能。在更新库存时使用排他锁,保证数据一致性。如
事务优化
- 减少事务粒度
- 方法:将大事务拆分成多个小事务。比如,将订单创建、库存更新和用户信息修改拆分成不同的事务,只要业务允许,在订单创建成功后,先提交订单创建事务,再进行库存更新事务等。这样可以减少锁的持有时间。
- 难点:可能会导致数据一致性问题,例如订单创建成功但库存更新失败。应对措施是引入补偿机制,当库存更新失败时,回滚订单或者进行库存回滚等操作,同时记录日志以便后续排查。
- 优化事务隔离级别
- 方法:根据业务场景选择合适的事务隔离级别。读未提交(Read Uncommitted)隔离级别并发性能最高,但可能会出现脏读问题;读已提交(Read Committed)能避免脏读,可满足大多数业务场景;可重复读(Repeatable Read)能避免不可重复读问题,InnoDB默认是可重复读级别;串行化(Serializable)是最高隔离级别,会对所有读取操作加锁,并发性能最低。对于电商业务,一般读已提交或可重复读能满足大部分场景。例如,对于一些统计类的查询,可将事务隔离级别临时调整为读未提交,提高查询性能,但要注意脏读风险。
- 难点:选择不当会影响数据一致性和并发性能。应对措施是通过性能测试和业务分析来确定最合适的隔离级别,测试不同隔离级别下的并发性能和数据一致性情况。
架构层面的改进
- 数据库主从复制与读写分离
- 方法:搭建主从复制架构,主库负责写操作(订单创建、库存更新、用户信息修改等),从库负责读操作(查询订单、库存信息等)。通过读写分离,减少主库的读压力,降低锁竞争。例如,使用MySQL的二进制日志(binlog)进行主从复制,应用程序配置多个数据源,写操作指向主库,读操作指向从库。
- 难点:主从数据同步延迟问题,可能导致读操作读到旧数据。应对措施是监控主从延迟,使用
SHOW STATUS LIKE 'Seconds_Behind_Master'
查看从库落后主库的时间,对于一些对数据实时性要求高的读操作,可暂时指向主库。
- 分布式缓存
- 方法:引入分布式缓存,如Redis。在订单创建前,先从缓存中查询库存信息,减少对数据库的直接访问。库存更新时,同时更新缓存。例如,在查询库存时,先从Redis中获取库存数据
GET product:123:stock
,如果没有再从数据库查询并更新缓存。 - 难点:缓存与数据库数据一致性问题,例如缓存更新成功但数据库更新失败。应对措施是采用合适的缓存更新策略,如先更新数据库再更新缓存,或者使用缓存失效策略,在一定时间后让缓存自动失效重新从数据库加载数据。同时,可引入分布式事务解决方案,如Seata,来保证缓存和数据库的一致性。
- 方法:引入分布式缓存,如Redis。在订单创建前,先从缓存中查询库存信息,减少对数据库的直接访问。库存更新时,同时更新缓存。例如,在查询库存时,先从Redis中获取库存数据
- 分库分表
- 方法:按照业务维度(如订单按用户ID、时间等)或数据量进行分库分表。例如,将订单表按用户ID进行哈希分表,每个表只存储部分用户的订单,减少单个表的数据量,降低锁竞争。使用MySQL的分区表功能或者中间件(如Mycat等)来实现分库分表。
- 难点:跨库跨表查询复杂,数据迁移和维护成本高。应对措施是在设计时尽量减少跨库跨表查询,通过冗余数据等方式来满足业务需求。对于数据迁移,可使用工具进行在线迁移,同时做好数据备份和恢复方案。