面试题答案
一键面试数据库架构设计
- 读写分离 原理:将数据库的读操作和写操作分离开,主库负责写,从库负责读。因为订单系统中读操作(如订单查询)可能远多于写操作(订单创建、库存更新),这样可以减轻主库压力,降低锁争用的可能性。 配合关系:在应用层,根据操作类型(读或写)将请求分发到对应的主库或从库。对于订单创建和库存更新这类写操作,发送到主库;对于订单查询等读操作,发送到从库。
- 分库分表
原理:
- 垂直分库:按业务模块将不同的表分到不同的数据库中,例如将订单表和用户表分别放在订单库和用户库。减少单个数据库的锁争用范围,因为不同业务模块的操作不会相互影响锁。
- 水平分表:以某个字段(如订单ID的哈希值)为依据,将大表拆分成多个小表。例如订单表按订单ID哈希取模,将数据分散到不同表中。这样在进行订单创建等操作时,锁的粒度更小,只锁定特定的分表。 配合关系:应用层需要根据业务逻辑和数据分布规则,正确定位操作的数据所在的库和表。在订单创建时,通过计算订单ID确定对应的分表,进行库存更新时,同样要根据库存相关的标识定位到具体库表。
事务隔离级别调整
- 选择合适的隔离级别
原理:
- 读未提交(Read Uncommitted):最低隔离级别,一个事务可以读取另一个未提交事务的数据。虽然可以减少锁等待时间,但可能出现脏读、不可重复读和幻读问题,在订单系统中一般不适用,因为订单数据的一致性要求较高。
- 读已提交(Read Committed):一个事务只能读取已提交事务的数据。可以避免脏读,但仍可能出现不可重复读和幻读。对于订单系统中一些实时性要求不高,但一致性要求较高的查询操作,可以考虑在部分场景使用。
- 可重复读(Repeatable Read):MySQL默认隔离级别,在一个事务内多次读取相同数据时,得到的结果是一致的,可避免脏读和不可重复读,但仍可能出现幻读。对于订单创建和库存更新这类事务,使用此级别可以保证事务执行过程中数据的一致性。
- 串行化(Serializable):最高隔离级别,事务串行执行,避免了所有并发问题,但性能最差,因为会产生大量锁等待,在高并发订单系统中不适合。 配合关系:在订单创建和库存更新事务中,选择可重复读隔离级别,确保数据一致性。对于一些查询类事务,如果对实时性要求不高,可以适当降低隔离级别到读已提交,以减少锁争用。
锁机制优化
- 优化锁粒度 原理:尽量使用行锁而非表锁。在订单系统中,订单创建和库存更新操作通常只涉及特定的行数据,使用行锁可以减少锁的范围,降低锁争用。例如,在更新库存时,只锁定库存表中对应商品的行记录。 配合关系:应用层在编写SQL语句时,要确保查询条件能够正确使用索引,使得数据库能够准确使用行锁。例如,在更新库存的SQL语句中,where条件使用商品ID作为索引字段,这样数据库就可以锁定具体的行,而不是整个表。
- 合理使用乐观锁 原理:乐观锁假设数据一般情况下不会产生并发冲突,在更新数据时先检查数据是否被其他事务修改。例如在库存更新时,先读取库存数量和版本号,更新时带上版本号,如果版本号不一致则说明数据已被修改,需要重新读取并尝试更新。 配合关系:在订单系统中,对于库存更新这类操作,如果并发冲突概率相对较低,可以使用乐观锁。在应用层代码中实现乐观锁逻辑,在数据库表中增加版本号字段。在库存更新事务开始时读取版本号,更新语句中带上版本号判断,若更新失败则重新获取数据并尝试更新。
缓存策略应用
- 订单缓存 原理:在应用层使用缓存(如Redis)缓存订单数据。对于一些查询频率高的订单信息,如订单详情、订单状态等,先从缓存中读取,若缓存中没有则从数据库读取并更新缓存。这样可以减少数据库读压力,降低锁争用。 配合关系:在订单创建成功后,将订单相关信息写入缓存。在订单查询时,先从缓存获取,缓存失效或不存在时从数据库读取并更新缓存。对于订单状态更新等操作,在数据库更新成功后及时更新缓存,保证缓存与数据库数据一致性。
- 库存缓存 原理:缓存库存数据,在进行库存更新操作前,先在缓存中进行预减库存。如果缓存中库存足够,则进行数据库库存更新,更新成功后再更新缓存;如果缓存中库存不足,则直接返回库存不足信息,避免数据库锁争用。 配合关系:应用层在库存更新事务开始时,先操作缓存库存。若缓存操作成功,再进行数据库库存更新,更新成功后同步更新缓存库存。为保证缓存与数据库一致性,可采用缓存失效策略,如定时刷新库存缓存,或者在数据库库存发生变化时及时通知缓存更新。