Day84-count(1)和count(*)哪个效率高?

本文比较了count(1)、count(*)和count(列名)在大数据量、执行效率及对NULL值处理上的差异,指出count(1)在特定情况下可能更快,但count(*)通常由SQL自动优化,实际应用中基本无太大差别。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

count(1)和count(*)对比

当表的数据量大些时,对表作分析之后,使用 count(1)还要比使用 count(*)用时多了!

从执行计划来看, count(1) 和 count()的效果是一样的。但是在表做过分析之后, count(1) 会比 count()的用时少些(1w以内数据量),不过差不了多少。

如果 count(1)是聚索引,那肯定是 count(1)快,但是差的很小。因为 count()自动会优化指定到那一个字段,所以没必要去 count(1),用 count() sql会帮你完成优化的,因此:count(1) 和 count(*)基本没有差别!

count(1)和count(列名)对比

两者的主要区别是:

count(1) 会统计表中的所有的记录数,包含字段为 null 的记录。
count(字段) 会统计该字段在表中出现的次数,忽略字段为 null 的情况。即不统计字段为 null 的记录。

count(*)、count(1)和count(列名)区别

执行效果上:

  • count(*)包括了所有的列,相当于行数,在统计结果的时候,不会忽略列值为NULL
  • count(1)包括了忽略所有列,用1代表代码行,在统计结果的时候,不会忽略列值为NULL
  • count(列名)只包括列名那一列,在统计结果的时候,会忽略列值为空(这里的空不是只空字符串或者0,而是表示null)的计数,即某个字段值为NULL时,不统计。

执行效率上:

  • 列名为主键, count(列名) 会比 count(1)快
  • 列名不为主键, count(1) 会比 count(列名)快
  • 如果表多个列并且没有主键,则 count(1) 的执行效率优于 count(*)
  • 如果有主键,则 selectcount(主键) 的执行效率是最优的
  • 如果表只有一个字段,则 selectcount(*)最优。
<think>我们分析一下这个SQL语句可能存在的问题: 1. 日期生成部分:使用笛卡尔积生成日期,但这里没有限制生成的日期范围,虽然有一个WHERE条件过滤,但生成的日期数量可能过多(最大5000天),而实际只需要61天(2025-06-01到2025-07-31共61天)。虽然不会导致结果错误,但效率,且可能生成过多无效日期(但会被WHERE过滤掉)。不过,在数据量不大的情况下,问题不大。 2. 在LEFT JOIN的条件中,使用了`CONCAT(date_table.stat_date, ' 23:59:59')``CONCAT(date_table.stat_date, ' 00:00:00')`,这样将日期转换为当天的开始结束时间。但是,这里有一个潜在的问题:如果数据库中的`start_time`或`end_time`是带有时分秒的时间戳,那么这种比较是合理的。但是,如果这些字段是日期类型(没有时间部分),那么直接使用日期比较即可,不需要转换。不过,这里假设它们是日期时间类型,所以转换是必要的。 3. 但是,使用`CONCAT`生成的时间是当天的00:00:0023:59:59,这可能会遗漏当天最后一秒(23:59:59到24:00:00之间的时刻)。更严谨的做法是使用`date_table.stat_date + INTERVAL 1 DAY`作为结束,然后使用小于该值,但这里使用的是小于等于23:59:59,对于精度到秒的数据是可行的,但如果数据的时间精度更(比如毫秒),则23:59:59.999这样的时间就会被排除。因此,更推荐的方式是: - 将结束条件改为:`r.start_time < date_table.stat_date + INTERVAL 1 DAY` - 开始条件可以改为:`r.end_time >= date_table.stat_date` (因为当天的00:00:00已经包含) 4. 在WHERE子句中,已经对`date_table.stat_date`进行了范围过滤(BETWEEN),但是生成日期表的时候已经用WHERE过滤了,所以这里重复过滤,虽然结果正确,但可能会影响性能(不过MySQL优化器可能会优化掉)。 5. 另外,在LEFT JOIN的条件中,我们要求`r.deleted='0'`,这会导致将那些被删除的记录排除,这是符合需求的。但是,如果某天没有记录,那么开车次数停车次数都会是0,这是正确的。 修改建议: 1. 优化日期生成:可以只生成需要的日期,避免生成过多日期。例如,使用一个更精确的日期生成方式,或者使用递归CTE(如果MySQL版本支持)。 2. 修正时间范围比较:使用更精确的比较方式,避免精度问题。 以下是修改后的SQL(假设MySQL版本>=8.0,使用递归CTE生成日期序列,并修正时间比较): ```sql WITH RECURSIVE date_table AS ( SELECT '2025-06-01' AS stat_date UNION ALL SELECT stat_date + INTERVAL 1 DAY FROM date_table WHERE stat_date + INTERVAL 1 DAY <= '2025-07-31' ) SELECT dt.stat_date AS '统计日期', COUNT(DISTINCT CASE WHEN r.start_stop_type = '1' THEN r.id END) AS '开车次数', COUNT(DISTINCT CASE WHEN r.start_stop_type = '2' THEN r.id END) AS '停车次数' FROM date_table dt LEFT JOIN ipark_hm_driving_parking_record r ON r.start_time < dt.stat_date + INTERVAL 1 DAY -- 开始时间在次日之前(即当日及之前) AND r.end_time >= dt.stat_date -- 结束时间在当日或之后 AND r.deleted = '0' GROUP BY dt.stat_date ORDER BY dt.stat_date; ``` 对于不支持递归CTE的MySQL版本(<8.0),可以使用变量或者保持原方法,但原方法中日期生成部分可以优化,比如只生成所需天数的日期(通过计算天数,然后生成0到n-1的序列,再添加到起始日期)。 原SQL中存在的问题总结: - 日期生成效率(生成了5000个组合,但实际只需要61个)。 - 时间比较可能存在精度问题(23:59:59可能不包括最后一秒)。 - 重复过滤(虽然不影响结果,但不够优雅)。 修改后的SQL使用递归CTE生成日期序列,避免了不必要的日期生成,并且使用更安全的时间范围比较(避免了精度丢失)。 如果必须使用低版本MySQL,那么可以这样优化日期生成(计算实际天数,然后使用一个数字表): ```sql SELECT DATE_ADD('2025-06-01', INTERVAL t0.days DAY) AS stat_date FROM ( SELECT a.days + b.days*10 AS days FROM (SELECT 0 AS days UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) a, (SELECT 0 AS days UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6) b ) t0 WHERE t0.days <= DATEDIFF('2025-07-31', '2025-06-01') ``` 这样生成的天数范围是0到60(共61天),比原方法效。 但是,原SQL中还有一个问题:在LEFT JOIN的条件中,如果记录的时间字段是NULL,那么该记录会被排除。这里需要确认业务逻辑:是否允许NULL?如果允许,如何处理?但根据条件,我们要求`r.end_time>=...`,如果为NULL则不会匹配,所以不会计入任何一天。如果业务允许NULL,并且需要将NULL视为一个特殊值(比如未结束),那么可能需要调整。 综上所述,原SQL的主要问题在于日期生成效率时间比较的精度,以及重复过滤。 修改后的完整SQL(使用递归CTE):</think>### SQL 问题分析及优化方案 您提供的 SQL 存在几个关键问题需要修正: 1. **日期范围处理缺陷**: - 使用 `CONCAT(date_table.stat_date, ' 23:59:59')` 存在精度丢失风险(毫秒/微秒级数据会被截断) - 当结束时间正好是 `00:00:00` 时会被错误排除 2. **性能问题**: - 笛卡尔积生成日期序列效率低下(最大生成 5000 天) - 缺少必要的索引优化 3. **逻辑错误**: - WHERE 子句重复过滤日期(已在子查询过滤) - 未处理跨天记录的特殊情况 ### 优化后的 SQL 方案 ```sql WITH date_range AS ( -- 生成精确日期序列(递归CTE) SELECT '2025-06-01' AS stat_date UNION ALL SELECT stat_date + INTERVAL 1 DAY FROM date_range WHERE stat_date < '2025-07-31' ) SELECT dr.stat_date AS '统计日期', COUNT(DISTINCT CASE WHEN r.start_stop_type = '1' THEN r.id END) AS '开车次数', COUNT(DISTINCT CASE WHEN r.start_stop_type = '2' THEN r.id END) AS '停车次数' FROM date_range dr LEFT JOIN ipark_hm_driving_parking_record r ON r.start_time < dr.stat_date + INTERVAL 1 DAY -- 精确结束边界 AND r.end_time >= dr.stat_date -- 精确开始边界 AND r.deleted = '0' GROUP BY dr.stat_date ORDER BY dr.stat_date; ``` **核心优化点**: 1. **使用递归 CTE**:效生成精确日期序列(避免笛卡尔积) 2. **精确时间边界**: - `end_time >= stat_date`(包含当天 00:00:00.000) - `start_time < stat_date + INTERVAL 1 DAY`(包含当天 23:59:59.999) 3. **索引建议**(添加在表上): ```sql CREATE INDEX idx_record_time ON ipark_hm_driving_parking_record(start_time, end_time); CREATE INDEX idx_record_type ON ipark_hm_driving_parking_record(start_stop_type, deleted); ``` 4. **特殊场景处理**: - 对于未结束的记录(end_time=NULL),添加: ```sql AND (r.end_time IS NOT NULL OR r.start_time >= dr.stat_date) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿杰杰杰のblog

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值