Đây là phần thứ hai của loạt blog gồm hai phần về Tối đa hóa Hiệu quả Truy vấn Cơ sở dữ liệu Trong MySQL. Bạn có thể đọc phần một tại đây.
Sử dụng Chỉ mục một cột, Tổng hợp, Tiền tố và Bao phủ
Các bảng thường xuyên nhận được lưu lượng truy cập cao phải được lập chỉ mục đúng cách. Việc lập chỉ mục bảng của bạn không chỉ quan trọng mà bạn còn cần xác định và phân tích đâu là loại truy vấn hoặc loại truy xuất mà bạn cần cho bảng cụ thể. Chúng tôi thực sự khuyên bạn nên phân tích loại truy vấn hoặc truy xuất dữ liệu nào bạn cần trên một bảng cụ thể trước khi quyết định chỉ mục nào là cần thiết cho bảng. Hãy xem xét các loại chỉ mục này và cách bạn có thể sử dụng chúng để tối đa hóa hiệu suất truy vấn của mình.
Chỉ mục Một Cột
Bảng InnoD có thể chứa tối đa 64 chỉ mục phụ. Chỉ mục một cột (hoặc chỉ mục toàn cột) là chỉ mục chỉ được gán cho một cột cụ thể. Tạo một chỉ mục cho một cột cụ thể có chứa các giá trị riêng biệt là một ứng cử viên tốt. Một chỉ mục tốt phải có số lượng và số liệu thống kê cao để trình tối ưu hóa có thể chọn kế hoạch truy vấn phù hợp. Để xem sự phân bổ của các chỉ mục, bạn có thể kiểm tra với cú pháp SHOW INDEXES như bên dưới:
root[test]#> SHOW INDEXES FROM users_account\G
*************************** 1. row ***************************
Table: users_account
Non_unique: 0
Key_name: PRIMARY
Seq_in_index: 1
Column_name: id
Collation: A
Cardinality: 131232
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
*************************** 2. row ***************************
Table: users_account
Non_unique: 1
Key_name: name
Seq_in_index: 1
Column_name: last_name
Collation: A
Cardinality: 8995
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
*************************** 3. row ***************************
Table: users_account
Non_unique: 1
Key_name: name
Seq_in_index: 2
Column_name: first_name
Collation: A
Cardinality: 131232
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE
Comment:
Index_comment:
3 rows in set (0.00 sec)
Bạn cũng có thể kiểm tra bằng các bảng information_schema.index_stosystem hoặc mysql.innodb_index_stats.
Chỉ mục hỗn hợp (Composite) hoặc nhiều phần
Chỉ số tổng hợp (thường được gọi là chỉ số tổng hợp) là một chỉ số nhiều phần bao gồm nhiều cột. MySQL cho phép tối đa 16 cột được giới hạn cho một chỉ mục tổng hợp cụ thể. Vượt quá giới hạn trả về một lỗi như sau:
ERROR 1070 (42000): Too many key parts specified; max 16 parts allowed
Chỉ mục tổng hợp cung cấp một sự thúc đẩy cho các truy vấn của bạn, nhưng nó đòi hỏi bạn phải có hiểu biết thuần túy về cách bạn đang truy xuất dữ liệu. Ví dụ:một bảng có DDL là ...
CREATE TABLE `user_account` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`last_name` char(30) NOT NULL,
`first_name` char(30) NOT NULL,
`dob` date DEFAULT NULL,
`zip` varchar(10) DEFAULT NULL,
`city` varchar(100) DEFAULT NULL,
`state` varchar(100) DEFAULT NULL,
`country` varchar(50) NOT NULL,
`tel` varchar(16) DEFAULT NULL
PRIMARY KEY (`id`),
KEY `name` (`last_name`,`first_name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
... bao gồm chỉ mục tổng hợp `tên`. Chỉ mục tổng hợp cải thiện hiệu suất truy vấn sau khi các khóa này được tham chiếu như các bộ phận chính được sử dụng. Ví dụ, hãy xem phần sau:
root[test]#> explain format=json select * from users_account where last_name='Namuag' and first_name='Maximus'\G
*************************** 1. row ***************************
EXPLAIN: {
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "1.20"
},
"table": {
"table_name": "users_account",
"access_type": "ref",
"possible_keys": [
"name"
],
"key": "name",
"used_key_parts": [
"last_name",
"first_name"
],
"key_length": "60",
"ref": [
"const",
"const"
],
"rows_examined_per_scan": 1,
"rows_produced_per_join": 1,
"filtered": "100.00",
"cost_info": {
"read_cost": "1.00",
"eval_cost": "0.20",
"prefix_cost": "1.20",
"data_read_per_join": "352"
},
"used_columns": [
"id",
"last_name",
"first_name",
"dob",
"zip",
"city",
"state",
"country",
"tel"
]
}
}
}
1 row in set, 1 warning (0.00 sec
used_key_parts cho thấy rằng kế hoạch truy vấn đã chọn một cách hoàn hảo các cột mong muốn được bao gồm trong chỉ mục tổng hợp của chúng tôi.
Lập chỉ mục tổng hợp cũng có những hạn chế của nó. Các điều kiện nhất định trong truy vấn không thể lấy tất cả các cột là một phần của khóa.
Tài liệu cho biết, "Trình tối ưu hóa cố gắng sử dụng các phần chính bổ sung để xác định khoảng thời gian miễn là toán tử so sánh là =, <=> hoặc LÀ KHÔNG ĐỦ. Nếu toán tử là> , <,> =, <=,! =, <>, GIỮA hoặc LIKE, trình tối ưu hóa sử dụng nó nhưng không xem xét các phần chính nữa. Đối với biểu thức sau, trình tối ưu hóa sử dụng =từ phép so sánh đầu tiên. Nó cũng sử dụng> =từ so sánh thứ hai nhưng không xem xét các bộ phận quan trọng khác và không sử dụng so sánh thứ ba để xây dựng khoảng… " . Về cơ bản, điều này có nghĩa là bất kể bạn có chỉ mục tổng hợp cho hai cột, truy vấn mẫu bên dưới không bao gồm cả hai trường:
root[test]#> explain format=json select * from users_account where last_name>='Zu' and first_name='Maximus'\G
*************************** 1. row ***************************
EXPLAIN: {
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "34.61"
},
"table": {
"table_name": "users_account",
"access_type": "range",
"possible_keys": [
"name"
],
"key": "name",
"used_key_parts": [
"last_name"
],
"key_length": "60",
"rows_examined_per_scan": 24,
"rows_produced_per_join": 2,
"filtered": "10.00",
"index_condition": "((`test`.`users_account`.`first_name` = 'Maximus') and (`test`.`users_account`.`last_name` >= 'Zu'))",
"cost_info": {
"read_cost": "34.13",
"eval_cost": "0.48",
"prefix_cost": "34.61",
"data_read_per_join": "844"
},
"used_columns": [
"id",
"last_name",
"first_name",
"dob",
"zip",
"city",
"state",
"country",
"tel"
]
}
}
}
1 row in set, 1 warning (0.00 sec)
Trong trường hợp này (và nếu truy vấn của bạn có nhiều phạm vi hơn thay vì các loại tham chiếu hoặc hằng số) thì hãy tránh sử dụng các chỉ mục tổng hợp. Nó chỉ lãng phí bộ nhớ và bộ đệm của bạn và nó làm tăng sự suy giảm hiệu suất của các truy vấn của bạn.
Chỉ mục tiền tố
Chỉ mục tiền tố là chỉ mục chứa các cột được tham chiếu như một chỉ mục, nhưng chỉ lấy độ dài bắt đầu được xác định cho cột đó và phần đó (hoặc dữ liệu tiền tố) là phần duy nhất được lưu trữ trong bộ đệm. Chỉ mục tiền tố có thể giúp giảm bớt tài nguyên vùng đệm và cả dung lượng ổ đĩa của bạn vì nó không cần chiếm toàn bộ chiều dài của cột. Điều này có nghĩa là gì? Hãy lấy một ví dụ. Hãy so sánh tác động giữa chỉ mục có độ dài đầy đủ so với chỉ mục tiền tố.
root[test]#> create index name on users_account(last_name, first_name);
Query OK, 0 rows affected (0.42 sec)
Records: 0 Duplicates: 0 Warnings: 0
root[test]#> \! du -hs /var/lib/mysql/test/users_account.*
12K /var/lib/mysql/test/users_account.frm
36M /var/lib/mysql/test/users_account.ibd
Chúng tôi đã tạo chỉ mục tổng hợp có độ dài đầy đủ sử dụng tổng cộng 36MiB không gian bảng cho bảng users_account. Hãy thả nó và sau đó thêm một chỉ mục tiền tố.
root[test]#> drop index name on users_account;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
root[test]#> alter table users_account engine=innodb;
Query OK, 0 rows affected (0.63 sec)
Records: 0 Duplicates: 0 Warnings: 0
root[test]#> \! du -hs /var/lib/mysql/test/users_account.*
12K /var/lib/mysql/test/users_account.frm
24M /var/lib/mysql/test/users_account.ibd
root[test]#> create index name on users_account(last_name(5), first_name(5));
Query OK, 0 rows affected (0.42 sec)
Records: 0 Duplicates: 0 Warnings: 0
root[test]#> \! du -hs /var/lib/mysql/test/users_account.*
12K /var/lib/mysql/test/users_account.frm
28M /var/lib/mysql/test/users_account.ibd
Sử dụng chỉ mục tiền tố, nó chỉ chứa tối đa 28MiB và ít hơn 8MiB so với sử dụng chỉ mục có độ dài đầy đủ. Điều đó thật tuyệt khi nghe, nhưng nó không có nghĩa là nó hiệu quả và phục vụ những gì bạn cần.
Nếu bạn quyết định thêm chỉ mục tiền tố, trước tiên bạn phải xác định loại truy vấn để truy xuất dữ liệu mà bạn cần. Tạo chỉ mục tiền tố giúp bạn sử dụng hiệu quả hơn với vùng đệm và do đó nó giúp ích cho hiệu suất truy vấn của bạn nhưng bạn cũng cần biết giới hạn của nó. Ví dụ:hãy so sánh hiệu suất khi sử dụng chỉ mục độ dài đầy đủ và chỉ mục tiền tố.
Hãy tạo chỉ mục có độ dài đầy đủ bằng cách sử dụng chỉ mục tổng hợp,
root[test]#> create index name on users_account(last_name, first_name);
Query OK, 0 rows affected (0.45 sec)
Records: 0 Duplicates: 0 Warnings: 0
root[test]#> EXPLAIN format=json select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G
*************************** 1. row ***************************
EXPLAIN: {
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "1.61"
},
"table": {
"table_name": "users_account",
"access_type": "ref",
"possible_keys": [
"name"
],
"key": "name",
"used_key_parts": [
"last_name",
"first_name"
],
"key_length": "60",
"ref": [
"const",
"const"
],
"rows_examined_per_scan": 3,
"rows_produced_per_join": 3,
"filtered": "100.00",
"using_index": true,
"cost_info": {
"read_cost": "1.02",
"eval_cost": "0.60",
"prefix_cost": "1.62",
"data_read_per_join": "1K"
},
"used_columns": [
"last_name",
"first_name"
]
}
}
}
1 row in set, 1 warning (0.00 sec)
root[test]#> flush status;
Query OK, 0 rows affected (0.02 sec)
root[test]#> pager cat -> /dev/null; select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G
PAGER set to 'cat -> /dev/null'
3 rows in set (0.00 sec)
root[test]#> nopager; show status like 'Handler_read%';
PAGER set to stdout
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 3 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+
7 rows in set (0.00 sec)
.
Bây giờ, hãy thử sử dụng chỉ mục tiền tố của cùng một phương pháp,
root[test]#> create index name on users_account(last_name(5), first_name(5));
Query OK, 0 rows affected (0.22 sec)
Records: 0 Duplicates: 0 Warnings: 0
root[test]#> EXPLAIN format=json select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G
*************************** 1. row ***************************
EXPLAIN: {
"query_block": {
"select_id": 1,
"cost_info": {
"query_cost": "3.60"
},
"table": {
"table_name": "users_account",
"access_type": "ref",
"possible_keys": [
"name"
],
"key": "name",
"used_key_parts": [
"last_name",
"first_name"
],
"key_length": "10",
"ref": [
"const",
"const"
],
"rows_examined_per_scan": 3,
"rows_produced_per_join": 3,
"filtered": "100.00",
"cost_info": {
"read_cost": "3.00",
"eval_cost": "0.60",
"prefix_cost": "3.60",
"data_read_per_join": "1K"
},
"used_columns": [
"last_name",
"first_name"
],
"attached_condition": "((`test`.`users_account`.`first_name` = 'Maximus Aleksandre') and (`test`.`users_account`.`last_name` = 'Namuag'))"
}
}
}
1 row in set, 1 warning (0.00 sec)
root[test]#> flush status;
Query OK, 0 rows affected (0.01 sec)
root[test]#> pager cat -> /dev/null; select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G
PAGER set to 'cat -> /dev/null'
3 rows in set (0.00 sec)
root[test]#> nopager; show status like 'Handler_read%';
PAGER set to stdout
+-----------------------+-------+
| Variable_name | Value |
+-----------------------+-------+
| Handler_read_first | 0 |
| Handler_read_key | 1 |
| Handler_read_last | 0 |
| Handler_read_next | 3 |
| Handler_read_prev | 0 |
| Handler_read_rnd | 0 |
| Handler_read_rnd_next | 0 |
+-----------------------+-------+
7 rows in set (0.00 sec)
MySQL tiết lộ rằng nó sử dụng chỉ mục đúng cách nhưng đáng chú ý là có chi phí cao hơn so với chỉ mục có độ dài đầy đủ. Đó là điều hiển nhiên và có thể giải thích được, vì chỉ mục tiền tố không bao gồm toàn bộ độ dài của các giá trị trường. Sử dụng chỉ mục tiền tố không phải là sự thay thế, cũng không phải là sự thay thế của lập chỉ mục có độ dài đầy đủ. Nó cũng có thể tạo ra kết quả kém khi sử dụng chỉ mục tiền tố không thích hợp. Vì vậy, bạn cần xác định loại truy vấn và dữ liệu bạn cần truy xuất.
Bao gồm các Chỉ mục
Việc bao gồm các Chỉ mục không yêu cầu bất kỳ cú pháp đặc biệt nào trong MySQL. Chỉ mục bao hàm trong InnoDB đề cập đến trường hợp tất cả các trường được chọn trong một truy vấn được bao phủ bởi một chỉ mục. Nó không cần thực hiện đọc tuần tự trên đĩa để đọc dữ liệu trong bảng mà chỉ sử dụng dữ liệu trong chỉ mục, tăng tốc đáng kể cho truy vấn. Ví dụ:truy vấn của chúng tôi trước đó, tức là
select last_name from users_account where last_name='Namuag' and first_name='Maximus Aleksandre' \G
Như đã đề cập trước đó, là một chỉ mục bao trùm. Khi bạn có các bảng được lập kế hoạch rất tốt khi lưu trữ dữ liệu của mình và tạo chỉ mục đúng cách, hãy cố gắng làm cho các truy vấn của bạn được thiết kế để tận dụng chỉ mục bao trùm để bạn có lợi cho kết quả. Điều này có thể giúp bạn tối đa hóa hiệu quả của các truy vấn và dẫn đến hiệu suất tuyệt vời.
Công cụ Đòn bẩy Cung cấp Cố vấn hoặc Giám sát Hiệu suất Truy vấn
Các tổ chức thường có xu hướng đầu tiên sử dụng github và tìm phần mềm nguồn mở có thể mang lại những lợi ích to lớn. Đối với các lời khuyên đơn giản giúp bạn tối ưu hóa các truy vấn của mình, bạn có thể tận dụng Bộ công cụ Percona. Đối với MySQL DBA, Percona Toolkit giống như một con dao quân đội Thụy Sĩ.
Đối với các hoạt động, bạn cần phân tích cách bạn đang sử dụng các chỉ mục của mình, bạn có thể sử dụng pt-index-using.
Pt-query-crypt cũng có sẵn và nó có thể phân tích các truy vấn MySQL từ nhật ký, danh sách xử lý và tcpdump. Trên thực tế, công cụ quan trọng nhất mà bạn phải sử dụng để phân tích và kiểm tra các truy vấn không hợp lệ là pt-query-dig. Sử dụng công cụ này để tổng hợp các truy vấn tương tự với nhau và báo cáo về những truy vấn tiêu tốn nhiều thời gian thực thi nhất.
Để lưu trữ các bản ghi cũ, bạn có thể sử dụng pt-archiver. Kiểm tra cơ sở dữ liệu của bạn để tìm các chỉ mục trùng lặp, tận dụng pt-trùng lặp-khóa-kiểm tra. Bạn cũng có thể tận dụng pt-deadlock-logger. Mặc dù bế tắc không phải là nguyên nhân của một truy vấn kém hiệu quả và kém hiệu quả mà là việc triển khai kém, nhưng nó ảnh hưởng đến tính kém hiệu quả của truy vấn. Nếu bạn cần bảo trì bảng và yêu cầu bạn thêm chỉ mục trực tuyến mà không ảnh hưởng đến lưu lượng cơ sở dữ liệu đi đến một bảng cụ thể, thì bạn có thể sử dụng pt-online-schema-change. Ngoài ra, bạn có thể sử dụng gh-ost, cũng rất hữu ích cho việc di chuyển giản đồ.
Nếu bạn đang tìm kiếm các tính năng dành cho doanh nghiệp, đi kèm với rất nhiều tính năng từ hiệu suất và giám sát truy vấn, cảnh báo và cảnh báo, bảng điều khiển hoặc số liệu giúp bạn tối ưu hóa các truy vấn và cố vấn, ClusterControl có thể là công cụ dành cho bạn. ClusterControl cung cấp nhiều tính năng hiển thị cho bạn Truy vấn hàng đầu, Truy vấn đang chạy và Ngoại trừ truy vấn. Hãy xem blog này MySQL Query Performance Tuning hướng dẫn bạn cách giám sát các truy vấn của bạn bằng ClusterControl.
Kết luận
Như bạn đã đến phần kết thúc của blog hai loạt bài của chúng tôi. Chúng tôi đã đề cập ở đây các yếu tố gây ra suy giảm truy vấn và cách giải quyết nó để tối đa hóa các truy vấn cơ sở dữ liệu của bạn. Chúng tôi cũng đã chia sẻ một số công cụ có thể mang lại lợi ích cho bạn và giúp giải quyết vấn đề của bạn.