LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

MySQL 中 count(*)、count(1)、count(字段)性能对比:一次彻底搞清楚

zhenglin
2025年12月5日 16:27 本文热度 80

团队 code review 时,一位同事把 count(*)改成了 count(1),说这样性能更好。真的是这样吗?今天通过源码和实测数据,把这个问题说透。

本文基于 MySQL 8.0.28 版本测试,不同版本的优化器行为可能有差异


三种 count 方式的本质区别

先看看这三种写法在 MySQL 中到底做了什么:


// 模拟MySQL处理count的伪代码

public class CountProcessor {


    // count(*) 的处理逻辑

    public long countStar(Table table) {

        long count = 0;

        for (Row row : table.getAllRows()) {

            // 只要行存在就计数,不管字段值

            count++;

        }

        return count;

    }


    // count(1) 的处理逻辑

    public long countOne(Table table) {

        long count = 0;

        for (Row row : table.getAllRows()) {

            // MySQL优化器会把count(1)转换为count(*)

            count++;

        }

        return count;

    }


    // count(字段) 的处理逻辑

    public long countField(Table table, String fieldName) {

        long count = 0;

        for (Row row : table.getAllRows()) {

            Object value = row.getField(fieldName);

            // 关键:只统计非NULL值

            if (value != null) {

                count++;

            }

        }

        return count;

    }

}

 

性能测试:用数据说话

创建测试环境,准备 1000 万条数据,多轮测试取平均值:


@Slf4j

@SpringBootTest

@TestPropertySource(properties = {

    "spring.datasource.hikari.maximum-pool-size=5",

    "spring.datasource.hikari.minimum-idle=1"

})

public class CountPerformanceTest {


    private static final DecimalFormat df = new DecimalFormat("#.##");


    @Autowired

    private JdbcTemplate jdbcTemplate;


    @Autowired

    private PlatformTransactionManager transactionManager;


    @BeforeEach

    @Transactional(propagation = Propagation.REQUIRES_NEW)

    public void setupTestData() {

        try {

            // 创建测试表

            jdbcTemplate.execute("""

                CREATE TABLE IF NOT EXISTS user_test (

                    id BIGINT PRIMARY KEY AUTO_INCREMENT,

                    username VARCHAR(50) NOT NULL,

                    email VARCHAR(100),

                    age INT,

                    city VARCHAR(50),

                    create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,

                    INDEX idx_username (username),

                    INDEX idx_email (email),

                    INDEX idx_city (city)

                ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

            """);


            // 使用事务批量插入

            TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);

            transactionTemplate.execute(status -> {

                insertTestDataInBatches();

                return null;

            });


            // 更新统计信息

            jdbcTemplate.execute("ANALYZE TABLE user_test");

            log.info("测试数据准备完成,表统计信息已更新");


        } catch (Exception e) {

            log.error("准备测试数据失败", e);

            throw new RuntimeException("测试数据初始化失败", e);

        }

    }


    private void insertTestDataInBatches() {

        List<Object[]> batchArgs = new ArrayList<>();

        String[] cities = {"北京", "上海", "广州", "深圳", "杭州"};


        for (int i = 1; i <= 10_000_000; i++) {

            String username = "user_" + i;

            String email = (i % 10 == 0) ? null : "user" + i + "@test.com";

            Integer age = (i % 20 == 0) ? null : 20 + (i % 50);

            String city = (i % 15 == 0) ? null : cities[i % cities.length];


            batchArgs.add(new Object[]{username, email, age, city});


            if (i % 10000 == 0) {

                jdbcTemplate.batchUpdate(

                    "INSERT INTO user_test (username, email, age, city) VALUES (?, ?, ?, ?)",

                    batchArgs

                );

                batchArgs.clear();

                if (i % 100000 == 0) {

                    log.info("已插入 {} 条数据", i);

                }

            }

        }

    }


    @Test

    public void testCountPerformance() {

        StopWatch stopWatch = new StopWatch("COUNT性能测试");

        try {

            warmUp();


            // 多次测试取平均值

            int testRounds = 5;

            Map<String, List<Long>> timings = new HashMap<>();


            for (int round = 0; round < testRounds; round++) {

                log.info("===== 第 {} 轮测试 =====", round + 1);

                performSingleRound(stopWatch, timings, round + 1);


                // 轮次间隔,避免缓存影响

                Thread.sleep(1000);

            }


            // 计算并输出平均值

            logAverageTimings(timings);


        } catch (InterruptedException e) {

            Thread.currentThread().interrupt();

            log.error("测试被中断", e);

        } finally {

            if (stopWatch.isRunning()) {

                stopWatch.stop();

            }

            log.info("性能测试总结:\n{}", stopWatch.prettyPrint());

        }

    }


    private void performSingleRound(StopWatch stopWatch, Map<String, List<Long>> timings, int round) {

        // 测试count(*)

        stopWatch.start("COUNT(*) - Round " + round);

        Long countStar = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM user_test", Long.class);

        stopWatch.stop();

        long time = stopWatch.getLastTaskTimeMillis();

        timings.computeIfAbsent("COUNT(*)", k -> new ArrayList<>()).add(time);

        log.info("COUNT(*) 结果: {}, 耗时: {} ms", countStar != null ? countStar : "null", time);


        // 测试count(1)

        stopWatch.start("COUNT(1) - Round " + round);

        Long countOne = jdbcTemplate.queryForObject("SELECT COUNT(1) FROM user_test", Long.class);

        stopWatch.stop();

        time = stopWatch.getLastTaskTimeMillis();

        timings.computeIfAbsent("COUNT(1)", k -> new ArrayList<>()).add(time);

        log.info("COUNT(1) 结果: {}, 耗时: {} ms", countOne != null ? countOne : "null", time);


        // 测试count(主键)

        stopWatch.start("COUNT(id) - Round " + round);

        Long countId = jdbcTemplate.queryForObject("SELECT COUNT(id) FROM user_test", Long.class);

        stopWatch.stop();

        time = stopWatch.getLastTaskTimeMillis();

        timings.computeIfAbsent("COUNT(id)", k -> new ArrayList<>()).add(time);

        log.info("COUNT(id) 结果: {}, 耗时: {} ms", countId != null ? countId : "null", time);


        // 测试count(索引列)

        stopWatch.start("COUNT(username) - Round " + round);

        Long countUsername = jdbcTemplate.queryForObject("SELECT COUNT(username) FROM user_test", Long.class);

        stopWatch.stop();

        time = stopWatch.getLastTaskTimeMillis();

        timings.computeIfAbsent("COUNT(username)", k -> new ArrayList<>()).add(time);

        log.info("COUNT(username) 结果: {}, 耗时: {} ms", countUsername != null ? countUsername : "null", time);


        // 测试count(非索引列)

        stopWatch.start("COUNT(age) - Round " + round);

        Long countAge = jdbcTemplate.queryForObject("SELECT COUNT(age) FROM user_test", Long.class);

        stopWatch.stop();

        time = stopWatch.getLastTaskTimeMillis();

        timings.computeIfAbsent("COUNT(age)", k -> new ArrayList<>()).add(time);

        log.info("COUNT(age) 结果: {}, 耗时: {} ms", countAge != null ? countAge : "null", time);

    }


    private void logAverageTimings(Map<String, List<Long>> timings) {

        log.info("\n===== 性能测试平均值 =====");

        timings.forEach((query, times) -> {

            double average = times.stream()

                .mapToLong(Long::longValue)

                .average()

                .orElse(0.0);

            log.info("{} 平均耗时: {} ms", query, df.format(average));

        });

    }


    private void warmUp() {

        log.info("开始预热查询...");

        for (int i = 0; i < 5; i++) {

            jdbcTemplate.queryForObject("SELECT COUNT(*) FROM user_test", Long.class);

        }

    }


    @AfterEach

    public void cleanup() {

        try {

            jdbcTemplate.execute("DROP TABLE IF EXISTS user_test");

            log.info("测试表清理完成");

        } catch (Exception e) {

            log.error("清理测试数据失败", e);

        }

    }

}


基准测试结果(基于 AWS RDS MySQL 8.0.28)

测试环境:

  • 实例规格:db.r6g.xlarge (4 vCPU, 32 GiB RAM)

  • 存储:1000 GiB GP3 SSD

  • 测试数据:1000 万条记录


场景COUNT(*)COUNT(1)COUNT(主键)COUNT(索引列)COUNT(非索引列)
冷查询2.13s2.15s2.31s2.45s8.76s
热查询0.18s0.18s0.21s0.23s7.89s
并行查询(4 线程)0.58s0.59s0.65s0.71s2.34s

结论:COUNT(*)和 COUNT(1)性能基本一致,COUNT(非索引列)性能最差。



存储引擎差异与索引统计

不同存储引擎对 COUNT 的处理方式差异很大:


@Component

@Slf4j

public class StorageEngineCountAnalyzer {


    @Autowired

    private JdbcTemplate jdbcTemplate;


    public void analyzeEngineSpecificBehavior() {

        // 查看表的存储引擎和统计信息

        String engineQuery = """

            SELECT

                t.table_name,

                t.engine,

                t.table_rows,

                t.avg_row_length,

                t.data_length,

                t.index_length

            FROM information_schema.tables t

            WHERE t.table_schema = DATABASE()

            AND t.table_name IN ('users', 'orders', 'products')

        """;


        List<Map<String, Object>> results = jdbcTemplate.queryForList(engineQuery);


        results.forEach(row -> {

            String tableName = (String) row.get("table_name");

            String engine = (String) row.get("engine");

            Long approximateRows = (Long) row.get("table_rows");


            log.info("表: {}, 引擎: {}, 近似行数: {}", tableName, engine, approximateRows);


            // 不同引擎的COUNT行为

            switch (engine) {

                case "MyISAM":

                    log.info("MyISAM引擎维护了准确的行数,COUNT(*)是O(1)操作");

                    break;

                case "InnoDB":

                    log.info("InnoDB引擎需要扫描索引统计行数,COUNT(*)是O(n)操作");

                    updateIndexStatistics(tableName);

                    break;

                case "Memory":

                    log.info("Memory引擎类似MyISAM,维护行数元数据");

                    break;

            }

        });

    }


    /**

     * 更新表的索引统计信息

     */

    public void updateIndexStatistics(String tableName) {

        try {

            // 更新索引统计信息

            jdbcTemplate.execute("ANALYZE TABLE " + tableName);


            // 查看索引基数和选择性

            String sql = """

                SELECT

                    s.index_name,

                    s.cardinality,

                    t.table_rows,

                    ROUND((s.cardinality / t.table_rows * 100), 2) as selectivity_percent

                FROM information_schema.statistics s

                JOIN information_schema.tables t

                    ON s.table_schema = t.table_schema

                    AND s.table_name = t.table_name

                WHERE s.table_schema = DATABASE()

                AND s.table_name = ?

                AND s.seq_in_index = 1

                ORDER BY s.cardinality DESC

            """;


            List<Map<String, Object>> stats = jdbcTemplate.queryForList(sql, tableName);

            stats.forEach(stat ->

                log.info("索引: {}, 基数: {}, 选择性: {}%",

                    stat.get("index_name"),

                    stat.get("cardinality"),

                    stat.get("selectivity_percent")

                )

            );

        } catch (Exception e) {

            log.error("更新表统计信息失败: {}", tableName, e);

        }

    }

}


执行计划成本分析与监控

通过 EXPLAIN 和性能监控深入分析 COUNT 的执行成本:

代码高亮:

@Component

@Slf4j

public class CountCostAnalyzer {


    @Autowired

    private JdbcTemplate jdbcTemplate;


    @Autowired

    private MeterRegistry meterRegistry;


    public void analyzeCountPlans() {

        String[] queries = {

            "SELECT COUNT(*) FROM user_test",

            "SELECT COUNT(1) FROM user_test",

            "SELECT COUNT(id) FROM user_test",

            "SELECT COUNT(username) FROM user_test",

            "SELECT COUNT(email) FROM user_test"

        };


        for (String query : queries) {

            analyzeQueryWithMetrics(query);

        }

    }


    private void analyzeQueryWithMetrics(String query) {

        Timer.Sample sample = Timer.Sample.start(meterRegistry);


        try {

            log.info("分析查询: {}", query);


            // EXPLAIN分析

            List<Map<String, Object>> explainResult = jdbcTemplate.queryForList("EXPLAIN " + query);


            for (Map<String, Object> row : explainResult) {

                log.info("type: {}, key: {}, rows: {}, Extra: {}",

                    row.get("type"),

                    row.get("key"),

                    row.get("rows"),

                    row.get("Extra")

                );

            }


            // 使用会话级profiling

            analyzeCostProfile(query);


            // 记录成功指标

            meterRegistry.counter("mysql.count.success", "query", extractQueryType(query))

                .increment();


        } catch (Exception e) {

            log.error("查询分析失败: {}", query, e);

            meterRegistry.counter("mysql.count.error", "query", extractQueryType(query))

                .increment();

        } finally {

            sample.stop(Timer.builder("mysql.count.duration")

                .tag("query", extractQueryType(query))

                .register(meterRegistry));

        }

    }


    public void analyzeCostProfile(String query) {

        jdbcTemplate.execute((ConnectionCallback<Void>) connection -> {

            try (Statement stmt = connection.createStatement()) {

                stmt.execute("SET profiling = 1");


                // 执行查询

                jdbcTemplate.queryForObject(query, Long.class);


                // 获取profile信息

                try (ResultSet rs = stmt.executeQuery("SHOW PROFILE")) {

                    while (rs.next()) {

                        double duration = rs.getDouble("Duration");

                        if (duration > 0.001) {

                            log.info("步骤: {}, 耗时: {}s",

                                rs.getString("Status"),

                                duration

                            );

                        }

                    }

                }

            } catch (SQLException e) {

                log.error("Profile分析失败", e);

            } finally {

                // 确保关闭profiling

                try {

                    connection.createStatement().execute("SET profiling = 0");

                } catch (SQLException e) {

                    log.warn("关闭profiling失败", e);

                }

            }

            return null;

        });

    }


    private String extractQueryType(String query) {

        if (query.contains("COUNT(*)")) return "count_star";

        if (query.contains("COUNT(1)")) return "count_one";

        if (query.contains("COUNT(id)")) return "count_id";

        if (query.contains("COUNT(email)")) return "count_email";

        return "count_other";

    }

}


生产环境实战场景

场景 1:高并发计数器实现(使用 Caffeine 缓存)


@Component

@Slf4j

public class ConcurrentCountManager {


    // 使用Caffeine缓存,更安全高效

    private final Cache<String, Long> countCache = Caffeine.newBuilder()

        .maximumSize(1000)

        .expireAfterWrite(5, TimeUnit.MINUTES)

        .recordStats()

        .build();


    @Autowired

    private JdbcTemplate jdbcTemplate;


    public long getCount(String tableName) {

        return countCache.get(tableName, this::queryCount);

    }


    private long queryCount(String tableName) {

        // 验证表名防止SQL注入

        if (!isValidTableName(tableName)) {

            throw new IllegalArgumentException("Invalid table name: " + tableName);

        }


        Long count = jdbcTemplate.queryForObject(

            "SELECT COUNT(*) FROM " + tableName, Long.class

        );

        return count != null ? count : 0L;

    }


    private boolean isValidTableName(String tableName) {

        return tableName != null && tableName.matches("^[a-zA-Z_][a-zA-Z0-9_]*$");

    }


    @Scheduled(fixedDelay = 60000)

    public void logCacheStats() {

        CacheStats stats = countCache.stats();

        log.info("缓存统计 - 命中率: {}, 加载次数: {}, 驱逐次数: {}",

            String.format("%.2f%%", stats.hitRate() * 100),

            stats.loadCount(),

            stats.evictionCount()

        );

    }

}

场景 2:分库分表场景的 COUNT 优化


@Service

@Slf4j

public class ShardingCountService {


    @Autowired

    private List<DataSource> shardDataSources;


    @Autowired

    private ExecutorService executorService;


    /**

     * 分库分表场景下的COUNT优化

     */

    public long getShardedCount(String logicalTableName) {

        CompletableFuture<Long>[] futures = new CompletableFuture[shardDataSources.size()];


        for (int i = 0; i < shardDataSources.size(); i++) {

            final int shardIndex = i;

            futures[i] = CompletableFuture.supplyAsync(() -> {

                JdbcTemplate shardJdbcTemplate = new JdbcTemplate(shardDataSources.get(shardIndex));

                String physicalTable = logicalTableName + "_" + shardIndex;


                try {

                    Long count = shardJdbcTemplate.queryForObject(

                        "SELECT COUNT(*) FROM " + physicalTable,

                        Long.class

                    );

                    log.debug("分片 {} 统计结果: {}", shardIndex, count);

                    return count != null ? count : 0L;

                } catch (Exception e) {

                    log.error("分片 {} 统计失败", shardIndex, e);

                    return 0L;

                }

            }, executorService);

        }


        // 汇总所有分片结果

        return CompletableFuture.allOf(futures)

            .thenApply(v -> Arrays.stream(futures)

                .mapToLong(f -> f.join())

                .sum()

            )

            .join();

    }

}

场景 3:COUNT 策略选择

@Service

@Slf4j

public class SmartCountStrategySelector {


    @Autowired

    private CountOptimizationConfig config;


    @Autowired

    private JdbcTemplate jdbcTemplate;


    @Autowired

    private ConcurrentCountManager countManager;


    @Autowired

    private RedisTemplate<String, Long> redisTemplate;


    /**

     * 选择最优的COUNT策略

     */

    public long getOptimizedCount(String tableName, CountContext context) {

        // 1. 评估表大小

        long approximateSize = getApproximateTableSize(tableName);


        // 2. 根据上下文选择策略

        CountOptimizationConfig.CountStrategy strategy = selectStrategy(

            approximateSize,

            context

        );


        log.debug("表 {} 选择策略: {}, 预估大小: {}",

            tableName, strategy, approximateSize);


        // 3. 执行对应策略

        return switch (strategy) {

            case DIRECT -> directCount(tableName);

            case CACHED -> countManager.getCount(tableName);

            case APPROXIMATE -> approximateCount(tableName);

            case COUNTER_TABLE -> counterTableCount(tableName);

        };

    }


    private CountOptimizationConfig.CountStrategy selectStrategy(

            long tableSize,

            CountContext context) {


        // 高精度要求

        if (context.isHighAccuracy()) {

            return tableSize < 1_000_000 ?

                CountOptimizationConfig.CountStrategy.DIRECT :

                CountOptimizationConfig.CountStrategy.COUNTER_TABLE;

        }


        // 高频查询

        if (context.getQueryFrequency() > 100) {

            return CountOptimizationConfig.CountStrategy.CACHED;

        }


        // 默认策略

        return config.selectStrategy(tableSize);

    }


    private long getApproximateTableSize(String tableName) {

        String sql = """

            SELECT table_rows

            FROM information_schema.tables

            WHERE table_schema = DATABASE()

            AND table_name = ?

        """;


        Long size = jdbcTemplate.queryForObject(sql, Long.class, tableName);

        return size != null ? size : 0L;

    }


    private long directCount(String tableName) {

        Long count = jdbcTemplate.queryForObject(

            "SELECT COUNT(*) FROM " + tableName, Long.class

        );

        return count != null ? count : 0L;

    }


    private long approximateCount(String tableName) {

        return getApproximateTableSize(tableName);

    }


    private long counterTableCount(String tableName) {

        String sql = "SELECT count_value FROM table_counters WHERE table_name = ?";

        Long count = jdbcTemplate.queryForObject(sql, Long.class, tableName);

        return count != null ? count : 0L;

    }


    @Data

    @Builder

    public static class CountContext {

        private boolean highAccuracy;

        private int queryFrequency; // 每分钟查询次数

        private boolean realTime;

        private long maxLatencyMs;

    }

}

场景 4:带熔断器的 COUNT 服务

代码高亮:

@Component

@Slf4j

public class CountCircuitBreaker {


    private final CircuitBreaker circuitBreaker;


    @Autowired

    private RedisTemplate<String, Long> redisTemplate;


    @Autowired

    private JdbcTemplate jdbcTemplate;


    public CountCircuitBreaker() {

        CircuitBreakerConfig config = CircuitBreakerConfig.custom()

            .failureRateThreshold(50)

            .waitDurationInOpenState(Duration.ofSeconds(30))

            .slidingWindowSize(10)

            .build();


        this.circuitBreaker = CircuitBreaker.of("countService", config);


        circuitBreaker.getEventPublisher()

            .onStateTransition(event ->

                log.warn("熔断器状态变更: {} -> {}",

                    event.getStateTransition().getFromState(),

                    event.getStateTransition().getToState())

            );

    }


    public long getCountWithFallback(String tableName, Supplier<Long> countSupplier) {

        return circuitBreaker.executeSupplier(() -> {

            log.debug("执行COUNT查询: {}", tableName);

            return countSupplier.get();

        }, throwable -> {

            log.error("COUNT查询失败,使用降级策略: {}", tableName, throwable);

            return getFallbackCount(tableName);

        });

    }


    private long getFallbackCount(String tableName) {

        // 降级策略优先级

        // 1. 尝试从Redis缓存获取

        String cacheKey = "count:fallback:" + tableName;

        Long cached = redisTemplate.opsForValue().get(cacheKey);

        if (cached != null) {

            log.info("使用Redis缓存值作为降级: {} = {}", tableName, cached);

            return cached;

        }


        // 2. 使用information_schema近似值

        try {

            String sql = """

                SELECT table_rows

                FROM information_schema.tables

                WHERE table_schema = DATABASE()

                AND table_name = ?

            """;

            Long approximate = jdbcTemplate.queryForObject(sql, Long.class, tableName);

            if (approximate != null) {

                log.info("使用近似值作为降级: {} ≈ {}", tableName, approximate);

                // 缓存近似值

                redisTemplate.opsForValue().set(cacheKey, approximate, Duration.ofMinutes(10));

                return approximate;

            }

        } catch (Exception e) {

            log.error("获取近似值失败", e);

        }


        // 3. 返回-1表示无法获取

        log.warn("所有降级策略失败: {}", tableName);

        return -1L;

    }

}

COUNT 优化配置类

@Configuration

@ConfigurationProperties(prefix = "mysql.count.optimization")

@Data

@Validated

public class CountOptimizationConfig {


    @NotNull

    @Min(1)

    private Integer cacheSize = 1000;


    @NotNull

    @Min(1)

    private Integer cacheExpireMinutes = 5;


    @NotNull

    private Boolean enableParallelQuery = true;


    @NotNull

    @Min(1)

    @Max(16)

    private Integer parallelThreads = 4;


    @NotNull

    private Boolean enableApproximateCount = true;


    @NotNull

    @Min(0)

    @Max(100)

    private Integer approximateThresholdPercent = 5;


    /**

     * 根据表大小自动选择COUNT策略

     */

    public CountStrategy selectStrategy(long tableSize) {

        if (tableSize < 10_000) {

            return CountStrategy.DIRECT;

        } else if (tableSize < 1_000_000) {

            return CountStrategy.CACHED;

        } else if (enableApproximateCount) {

            return CountStrategy.APPROXIMATE;

        } else {

            return CountStrategy.COUNTER_TABLE;

        }

    }


    public enum CountStrategy {

        DIRECT,         // 直接查询

        CACHED,         // 使用缓存

        APPROXIMATE,    // 近似值

        COUNTER_TABLE   // 计数器表

    }

}

MySQL 8.0 新特性对 COUNT 的影响

@Component

@Slf4j

public class MySQL8CountOptimizer {


    @Autowired

    private JdbcTemplate jdbcTemplate;


    /**

     * MySQL 8.0 并行查询配置

     */

    @PostConstruct

    public void configureParallelQuery() {

        // 检查MySQL版本

        String version = jdbcTemplate.queryForObject(

            "SELECT VERSION()", String.class

        );


        if (version != null && version.startsWith("8.")) {

            log.info("MySQL 8.0 detected, configuring parallel query");


            // 预热查询计划缓存

            warmupQueryPlanCache();

        }

    }


    /**

     * 预热查询计划缓存

     */

    private void warmupQueryPlanCache() {

        List<String> criticalTables = Arrays.asList("users", "orders", "products");


        criticalTables.forEach(table -> {

            try {

                // 预热不同类型的COUNT查询

                jdbcTemplate.queryForObject("SELECT COUNT(*) FROM " + table, Long.class);


                // 预热带条件的COUNT

                jdbcTemplate.queryForObject(

                    "SELECT COUNT(*) FROM " + table + " WHERE id > ?",

                    Long.class,

                    0

                );


                log.info("查询计划缓存预热完成: {}", table);

            } catch (Exception e) {

                log.warn("查询计划缓存预热失败: {}", table, e);

            }

        });

    }


    public void testParallelQueryPerformance() {

        StopWatch watch = new StopWatch();


        // 禁用并行

        watch.start("COUNT without parallel");

        jdbcTemplate.execute("SET SESSION innodb_parallel_read_threads = 1");

        Long count1 = jdbcTemplate.queryForObject(

            "SELECT COUNT(*) FROM large_table", Long.class

        );

        watch.stop();


        // 启用并行

        watch.start("COUNT with parallel");

        jdbcTemplate.execute("SET SESSION innodb_parallel_read_threads = 4");

        Long count2 = jdbcTemplate.queryForObject(

            "SELECT COUNT(*) FROM large_table", Long.class

        );

        watch.stop();


        log.info("并行查询性能对比:\n{}", watch.prettyPrint());

    }

}

决策流程图


时间复杂度分析

  

注:n 为表的总行数

最佳实践

  • 统计总行数:使用 COUNT(*),语义清晰,性能最优


  • 统计非空值:使用 COUNT(column),注意 NULL 值影响


  • 大表优化:考虑缓存、近似值或分片统计


  • 避免在循环中 COUNT:使用 GROUP BY 一次查询


  • 注意存储引擎差异:MyISAM 的 COUNT(*)是 O(1),InnoDB 是 O(n)


  • 监控慢查询:设置合理的 slow_query_log 阈值


  • 分页优化:避免每次都 COUNT,考虑游标分页


  • 安全防护:永远不要直接拼接 SQL,使用参数化查询


  • MySQL 8.0 优化:启用并行查询提升大表 COUNT 性能


  • 生产环境策略:组合使用计数器表、缓存和异步更新


  • 高可用保障:使用熔断器防止雪崩,提供降级方案


  • 分库分表:并行统计各分片,注意连接池大小



总结

参考文章:原文链接


该文章在 2025/12/5 16:27:46 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2025 ClickSun All Rights Reserved