MySQL Đếm số dòng hiệu suất cao: Phân biệt và lựa chọn giữa COUNT(*), COUNT(1), COUNT(cột)

Giới thiệu

Trong phát triển ứng dụng, việc đếm số lượng bản ghi trong MySQL là thao tác phổ biến. Tuy nhiên, giữa COUNT(*), COUNT(1)COUNT(tên_cột), nhiều lập trình viên thường băn khoăn: chúng khác nhau thế nào? Cách nào nhanh hơn? Nên dùng khi nào để tránh lỗi hiệu suất? Bài viết này sẽ làm rõ thông qua phân tích thực tế.

1. Kết luận tổng quan: Sự khác biệt cốt lõi

Môi trường kiểm thử: MySQL 8.0.40, sử dụng bộ máy InnoDB
Gợi ý: Dùng lệnh EXPLAIN SELECT COUNT(...) để xem kế hoạch thực thi mà bộ tối ưu hóa chọn.

Xét bảng người dùng nguoi_dung với cột email cho phép giá trị NULL:

CREATE TABLE nguoi_dung (
    id INT PRIMARY KEY AUTO_INCREMENT,
    ten VARCHAR(50),
    email VARCHAR(100)
);

INSERT INTO nguoi_dung (ten, email) VALUES
('Zhang San', 'zhangsan@example.com'),
('Li Si', NULL),
('Wang Wu', 'wangwu@example.com'),
('Zhao Liu', NULL);

Thực hiện các truy vấn sau:

SELECT COUNT(*) FROM nguoi_dung;     -- Kết quả: 4 (tất cả bản ghi)
SELECT COUNT(1) FROM nguoi_dung;      -- Kết quả: 4 (tương đương COUNT(*))
SELECT COUNT(email) FROM nguoi_dung;  -- Kết quả: 2 (chỉ đếm những email không NULL)
SELECT COUNT(id) FROM nguoi_dung;     -- Kết quả: 4 (id là khóa chính, luôn có giá trị)

Tóm tắt sự khác biệt:

  • COUNT(*): Đếm toàn bộ số dòng trong bảng, bất kể giá trị cột nào có phải NULL hay không.
  • COUNT(1): Về mặt logic và hiệu suất, hoàn toàn tương đương với COUNT(*).
  • COUNT(ten_cot): Chỉ đếm những dòng mà ten_cot không phải NULL.

2. So sánh hiệu suất: Câu lệnh nào nhanh hơn?

Nhiều người cho rằng COUNT(1) nhanh hơn COUNT(*), hoặc COUNT(khoá_chính) là lựa chọn tối ưu. Thực tế ra sao? Hãy cùng kiểm chứng.

Chuẩn bị thử nghiệm: Bảng dữ liệu lớn

CREATE TABLE bang_lon (
    id INT PRIMARY KEY AUTO_INCREMENT,
    du_lieu VARCHAR(255),
    INDEX idx_du_lieu (du_lieu)
);

Chèn khoảng 1 triệu bản ghi để kiểm tra hiệu năng.

Thử nghiệm 1: COUNT(*) vs COUNT(1)

SELECT COUNT(*) FROM bang_lon;
SELECT COUNT(1) FROM bang_lon;

Kết quả từ EXPLAIN cho thấy cả hai đều sử dụng cùng một chiến lược quét chỉ mục, thời gian thực thi gần như bằng nhau.

Kết luận: Không có khác biệt đáng kể về hiệu suất. Bộ tối ưu hóa MySQL xử lý COUNT(*) như một trường hợp đặc biệt, thậm chí còn được ưu tiên tối ưu hơn COUNT(1). Vì vậy, nên ưu tiên dùng COUNT(*) vì tính rõ nghĩa và chuẩn mực.

Thử nghiệm 2: COUNT(*) vs COUNT(khoá_chính) vs COUNT(chỉ_mục)

SELECT COUNT(id) FROM bang_lon;           -- Khóa chính
SELECT COUNT(du_lieu) FROM bang_lon;       -- Cột có chỉ mục
SELECT COUNT(*) FROM bang_lon;             -- Tất cả bản ghi

Kết quả EXPLAIN cho thấy tất cả đều sử dụng chỉ mục phụ idx_du_lieu để quét — điều này chứng tỏ MySQL tự động chọn đường đi ngắn nhất.

Thứ tự hiệu suất: COUNT(*)COUNT(du_lieu) > COUNT(id)

  • COUNT(*): Hiệu quả cao nhất. MySQL biết đây là thao tác đếm thuần túy nên bỏ qua dữ liệu gốc, chỉ duyệt chỉ mục nhỏ nhất (thường là chỉ mục phụ), đồng thời giảm tải kiểm tra MVCC.
  • COUNT(cột_có_chỉ_mục): Khá nhanh nếu cột đó có chỉ mục, nhưng vẫn cần kiểm tra điều kiện NOT NULL.
  • COUNT(khoá_chính): Có thể chậm hơn do liên quan đến cấu trúc index clustered, dẫn tới I/O lớn hơn khi duyệt.

3. Trường hợp sử dụng phù hợp

Trường hợp 1: Đếm tổng số bản ghi (ví dụ: phân trang)

-- ✅ Khuyên dùng
SELECT COUNT(*) FROM nguoi_dung;

-- ❌ Không cần thiết
SELECT COUNT(1) FROM nguoi_dung;

Trường hợp 2: Đếm số lượng giá trị hợp lệ của một cột

-- ✅ Đếm người dùng có email
SELECT COUNT(email) FROM nguoi_dung;

Trường hợp 3: Đếm số lượng phần tử duy nhất

-- ✅ Đếm số email khác nhau
SELECT COUNT(DISTINCT email) FROM nguoi_dung;

4. Những hiểu lầm phổ biến

Sai lầm 1: "MyISAM luôn nhanh hơn với COUNT(*)"

Đúng là MyISAM lưu sẵn số dòng, nhưng chỉ áp dụng với câu lệnh:

SELECT COUNT(*) FROM bang_myisam;

Nếu thêm điều kiện:

SELECT COUNT(*) FROM bang_myisam WHERE ten LIKE 'A%';

—> Vẫn phải quét dữ liệu theo thời gian thực, mất nhiều thời gian.

Sai lầm 2: "COUNT(khoá_chính) luôn tốt nhất"

Theo kết quả thử nghiệm, COUNT(*) thường nhanh hơn hoặc bằng COUNT(khoá_chính) do được tối ưu sâu hơn bởi engine.

5. Gợi ý lựa chọn theo ngữ cảnh

Tình huống Cách viết khuyên dùng Lý do
Đếm tất cả bản ghi COUNT(*) Rõ nghĩa, được tối ưu tốt nhất
Đếm số giá trị không NULL COUNT(ten_cot) Tự loại bỏ NULL
Đếm trên cột có chỉ mục COUNT(ten_cot) Hiệu suất khá nếu đã có chỉ mục
Đếm giá trị riêng biệt COUNT(DISTINCT ...) Loại bỏ trùng lặp

6. Tổng kết

  • Dùng COUNT(*) khi cần đếm toàn bộ bản ghi — đây là lựa chọn an toàn và hiệu quả nhất.
  • Tránh dùng COUNT(cột_không_có_chỉ_mục) trên bảng lớn.
  • Không tin vào "kinh nghiệm truyền miệng", hãy dùng EXPLAIN để kiểm tra kế hoạch thực thi thực tế.

Thẻ: mysql innodb COUNT(*) SQL Optimization Query Performance

Đăng vào ngày 27 tháng 6 lúc 18:35