1. 题目背景与需求分析
力扣(LeetCode)1757题"可回收且低脂的产品"是一道典型的数据库查询题目,主要考察对MySQL基础查询语句的掌握程度。题目要求从一个产品表中筛选出同时满足"可回收"(recyclable)和"低脂"(low_fat)两个条件的产品。
在实际业务场景中,这类查询非常常见。比如在电商系统中,用户可能希望筛选出"有机认证"且"价格低于100元"的商品;在内容平台,可能需要找出"原创"且"阅读量超过1000"的文章。理解这类问题的解法,对日常开发工作有直接帮助。
2. MySQL表结构设计
2.1 基础表结构
根据题目要求,我们需要创建一个产品表(Products),至少包含以下字段:
CREATE TABLE Products ( product_id INT PRIMARY KEY, low_fats ENUM('Y', 'N') NOT NULL, recyclable ENUM('Y', 'N') NOT NULL );这里有几个设计考虑:
- 使用ENUM类型而非VARCHAR存储Y/N标志,可以节省存储空间(ENUM只需1字节)
- 将product_id设为主键,确保产品唯一性
- NOT NULL约束保证数据完整性
2.2 扩展设计建议
实际业务中,产品表通常会更复杂。我们可以考虑以下扩展字段:
CREATE TABLE Products ( product_id INT PRIMARY KEY, product_name VARCHAR(100) NOT NULL, low_fats ENUM('Y', 'N') NOT NULL, recyclable ENUM('Y', 'N') NOT NULL, category_id INT, price DECIMAL(10,2), stock_quantity INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (category_id) REFERENCES Categories(category_id) );提示:在真实项目中,建议为频繁查询的字段(如low_fats和recyclable)添加索引,特别是当表数据量较大时。
3. 查询解决方案
3.1 基础解法
题目要求的查询非常简单:
SELECT product_id FROM Products WHERE low_fats = 'Y' AND recyclable = 'Y';这个查询使用了最基本的WHERE条件组合,通过AND运算符确保两个条件同时满足。
3.2 性能优化考虑
虽然这个查询很简单,但在大数据量下仍有优化空间:
索引优化:为low_fats和recyclable创建复合索引
CREATE INDEX idx_eco_friendly ON Products(low_fats, recyclable);覆盖索引:如果只需要product_id,可以创建包含所有查询字段的索引
CREATE INDEX idx_covering ON Products(low_fats, recyclable, product_id);查询重写:在某些MySQL版本中,以下写法可能更高效
SELECT product_id FROM Products WHERE (low_fats, recyclable) = ('Y', 'Y');
4. 实际应用场景扩展
4.1 动态条件查询
在实际业务中,查询条件往往是动态的。我们可以使用预处理语句:
PREPARE stmt FROM 'SELECT product_id FROM Products WHERE low_fats = ? AND recyclable = ?'; SET @low_fat = 'Y'; SET @recyclable = 'Y'; EXECUTE stmt USING @low_fat, @recyclable; DEALLOCATE PREPARE stmt;4.2 分页查询
当结果集很大时,需要添加分页:
SELECT product_id FROM Products WHERE low_fats = 'Y' AND recyclable = 'Y' LIMIT 10 OFFSET 0; -- 第一页4.3 结果排序
通常还需要对结果排序:
SELECT product_id FROM Products WHERE low_fats = 'Y' AND recyclable = 'Y' ORDER BY product_id DESC LIMIT 10;5. 常见错误与调试技巧
5.1 数据类型不匹配
新手常犯的错误是忘记ENUM值需要用引号括起来:
-- 错误写法 WHERE low_fats = Y AND recyclable = Y; -- 正确写法 WHERE low_fats = 'Y' AND recyclable = 'Y';5.2 索引失效场景
即使创建了索引,某些写法会导致索引失效:
-- 索引可能失效的写法 WHERE low_fats = 'Y' OR recyclable = 'Y'; -- 更好的写法(如果需要OR逻辑) WHERE low_fats = 'Y' UNION WHERE recyclable = 'Y';5.3 执行计划分析
使用EXPLAIN分析查询性能:
EXPLAIN SELECT product_id FROM Products WHERE low_fats = 'Y' AND recyclable = 'Y';关键看:
- type列是否为ref或range
- key列是否使用了正确的索引
- rows列预估扫描行数
6. 高级应用:视图与存储过程
6.1 创建视图
对于频繁使用的查询,可以创建视图:
CREATE VIEW EcoFriendlyProducts AS SELECT product_id FROM Products WHERE low_fats = 'Y' AND recyclable = 'Y';使用时直接查询视图:
SELECT * FROM EcoFriendlyProducts;6.2 存储过程封装
将业务逻辑封装为存储过程:
DELIMITER // CREATE PROCEDURE GetEcoFriendlyProducts(IN page INT, IN page_size INT) BEGIN DECLARE offset_val INT; SET offset_val = (page - 1) * page_size; SELECT product_id FROM Products WHERE low_fats = 'Y' AND recyclable = 'Y' LIMIT page_size OFFSET offset_val; END // DELIMITER ;调用方式:
CALL GetEcoFriendlyProducts(1, 10); -- 获取第一页,每页10条7. 数据库设计最佳实践
7.1 命名规范
- 表名使用复数形式(Products而非Product)
- 布尔类型字段使用is_前缀或明确的Y/N标志
- 主键字段统一命名为id或{table_name}_id
7.2 数据类型选择
- 标志字段:ENUM('Y','N')或TINYINT(1)
- 字符串:根据长度选择CHAR/VARCHAR
- 数值:根据范围选择TINYINT/SMALLINT/INT/BIGINT
- 金额:DECIMAL(precision, scale)
7.3 约束与验证
- 非空约束:NOT NULL
- 默认值:DEFAULT
- 外键约束:FOREIGN KEY
- 检查约束:CHECK(MySQL 8.0+)
8. 实际业务中的变体问题
8.1 多条件组合查询
实际业务中常需要更复杂的条件组合:
SELECT product_id FROM Products WHERE (low_fats = 'Y' AND recyclable = 'Y') OR (price < 100 AND stock_quantity > 0);8.2 关联查询
通常需要关联其他表获取更多信息:
SELECT p.product_id, p.product_name, c.category_name FROM Products p JOIN Categories c ON p.category_id = c.category_id WHERE p.low_fats = 'Y' AND p.recyclable = 'Y';8.3 聚合查询
统计环保产品的数量:
SELECT COUNT(*) AS eco_friendly_count FROM Products WHERE low_fats = 'Y' AND recyclable = 'Y';按类别统计:
SELECT c.category_name, COUNT(*) AS eco_count FROM Products p JOIN Categories c ON p.category_id = c.category_id WHERE p.low_fats = 'Y' AND p.recyclable = 'Y' GROUP BY c.category_name;9. 性能对比测试
9.1 测试数据准备
创建100万条测试数据:
-- 创建测试表 CREATE TABLE TestProducts ( id INT AUTO_INCREMENT PRIMARY KEY, low_fats ENUM('Y', 'N'), recyclable ENUM('Y', 'N') ); -- 插入随机数据 INSERT INTO TestProducts (low_fats, recyclable) SELECT IF(RAND() < 0.5, 'Y', 'N'), IF(RAND() < 0.5, 'Y', 'N') FROM (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t1, (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t2, (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t3, (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t4, (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t5, (SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 4) t6;9.2 不同查询方式性能对比
- 无索引基础查询:
SELECT id FROM TestProducts WHERE low_fats = 'Y' AND recyclable = 'Y'; -- 执行时间:约1200ms- 有单列索引查询:
CREATE INDEX idx_low_fats ON TestProducts(low_fats); CREATE INDEX idx_recyclable ON TestProducts(recyclable); SELECT id FROM TestProducts WHERE low_fats = 'Y' AND recyclable = 'Y'; -- 执行时间:约800ms- 复合索引查询:
CREATE INDEX idx_eco ON TestProducts(low_fats, recyclable); SELECT id FROM TestProducts WHERE low_fats = 'Y' AND recyclable = 'Y'; -- 执行时间:约50ms10. 跨数据库兼容性考虑
10.1 PostgreSQL差异
在PostgreSQL中,ENUM类型需要先创建类型:
CREATE TYPE eco_flag AS ENUM ('Y', 'N'); CREATE TABLE Products ( product_id SERIAL PRIMARY KEY, low_fats eco_flag NOT NULL, recyclable eco_flag NOT NULL );10.2 SQL Server差异
SQL Server中使用BIT或CHAR(1):
CREATE TABLE Products ( product_id INT PRIMARY KEY, low_fats CHAR(1) CHECK (low_fats IN ('Y', 'N')), recyclable CHAR(1) CHECK (recyclable IN ('Y', 'N')) );10.3 Oracle差异
Oracle中使用CHAR(1)加检查约束:
CREATE TABLE Products ( product_id NUMBER PRIMARY KEY, low_fats CHAR(1) CHECK (low_fats IN ('Y', 'N')), recyclable CHAR(1) CHECK (recyclable IN ('Y', 'N')) );11. 实际项目中的经验分享
在真实项目中处理类似查询时,我总结了几点经验:
尽早考虑分页:即使当前数据量小,也要预先设计分页方案,避免后期数据增长导致性能问题。
监控慢查询:定期检查慢查询日志,发现性能不佳的查询及时优化。
考虑缓存策略:对于变化不频繁但查询频繁的数据,可以考虑使用Redis等缓存。
字段注释很重要:为所有字段添加注释,特别是Y/N这种枚举值:
ALTER TABLE Products MODIFY COLUMN low_fats ENUM('Y','N') COMMENT 'Y=低脂,N=非低脂';测试不同MySQL版本:不同版本的MySQL对查询优化器的实现有差异,特别是5.7和8.0之间有较大变化。