[英]Slow count query (mariaDB)
我的查詢速度有問題。 簡單的mysql查詢,但是當我有很多記錄(當前> 1 000 000)時,性能真的很慢。 問題與此類似,但找不到解決方案。 解釋說 MySQL 正在使用:Using where; 使用索引。 有沒有人有任何建議可以加快速度? 慢查詢:
select sql_no_cache
count(distinct(`books`.`id`)) as `count`
from `books`
left join `books_genres` use index(categorie_id) on `books_genres`.`book_id` = `books`.`id`
where 1 and `books_genres`.`genre_id` in(307,380,385,384,1359,381,390,397,394,949,1390,1391,403,401,1332,393,398,1374,1397,402,984,1025,841,1027,359,577,365,1021,1023,360,368,369,370,942,1061,1348,1376,437,737,1137,1354,1384,1385,1115,1113,1114,1143,1363,593,581,583,567,978,973,576,677,825,595,826,1043,827,1077,323,324,1361,1362,1360,407,610,611,1179,608,336,831,1042,520,1079,1078,1081,352,1349,388,727,728,729,325,330,1099,616,320,1375,1138,1388,1119,1141,1140,1328,1136,1044,1103,1074,1150,1322) and `books`.`is_status` = 'active' and `books`.`master_book` = 'true'
結果:
+--------+
| count |
+--------+
| 402545 |
+--------+
1 row in set (2.64 sec)
解釋:
+------+-------------+--------------+--------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+---------+--------------------------------+---------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+--------------+--------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+---------+--------------------------------+---------+--------------------------+
| 1 | SIMPLE | books_genres | index | categorie_id | categorie_id | 4 | NULL | 1866077 | Using where; Using index |
| 1 | SIMPLE | books | eq_ref | PRIMARY,is_status,master_book,is_status_master_book,is_status_master_book_indexed,is_status_donor_no_ru_master_book,is_status_indexed,books_idx_is_stat_master_livelib_id,master_book_is_status,is_status_master_book_year | PRIMARY | 4 | base.books_genres.book_id | 1 | Using where |
+------+-------------+--------------+--------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+--------------+---------+--------------------------------+---------+--------------------------+
2 rows in set (0.10 sec)
我的表:
CREATE TABLE `books` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`donor` enum('default','ihavebook','flibusta','litres','book24','labirint','livelib') NOT NULL DEFAULT 'default' COMMENT 'Донор',
`donor_id` varchar(200) DEFAULT NULL COMMENT 'ID у донора',
`name` varchar(256) DEFAULT NULL COMMENT 'Название',
`name_int` text COMMENT 'Оригинальное название',
`name_alt` text COMMENT 'Альтернативные названия',
`year` int(11) DEFAULT NULL COMMENT 'Дата выхода',
`poster` varchar(256) DEFAULT NULL COMMENT 'Путь к изображению',
`description` text COMMENT 'Описание',
`rating` double(4,2) NOT NULL DEFAULT '0.00' COMMENT 'Рейтинг',
`rating_count` int(11) NOT NULL DEFAULT '0' COMMENT 'Сколько проголосовали',
`view_count` int(11) NOT NULL DEFAULT '0' COMMENT 'Количество просмотров',
`allowed_fragment` enum('5','10','15','20','30','40','50','60','70') DEFAULT NULL COMMENT 'Разрешенный фрагмент правообладателем (если is_toread = false)',
`is_status` enum('active','parser','incorrect','extremist','delete','fulldeteled') NOT NULL DEFAULT 'active' COMMENT 'Статус книги',
`is_toread` enum('true','false') NOT NULL DEFAULT 'false' COMMENT 'Возможность читать онлайн',
`is_similar` enum('true','false') NOT NULL DEFAULT 'false' COMMENT 'Возможность посмотреть список похожих книг',
`is_partners` enum('true','false') NOT NULL DEFAULT 'false' COMMENT 'Наличие партнерских ссылок',
`is_duplicate` enum('true','false') NOT NULL DEFAULT 'false' COMMENT 'Проверка на дубль',
`is_rightholder_lock` enum('true','false') NOT NULL DEFAULT 'false' COMMENT 'блокировка правообладателя',
`rating_litres` int(11) DEFAULT NULL COMMENT 'рейтинг Литрес',
`date_written` date DEFAULT NULL,
`is_genre_deleted` tinyint(1) DEFAULT NULL,
`is_description_blocked` enum('false','true') DEFAULT 'false' COMMENT 'не давать парсерам менять описание',
`translator` varchar(255) DEFAULT NULL COMMENT 'Переводчики',
`genres_count` tinyint(4) DEFAULT NULL,
`checked_admin` enum('false','true') DEFAULT 'false',
`no_ru` enum('false','true') DEFAULT 'false' COMMENT 'не русская версия',
`count_authors` int(11) DEFAULT NULL,
`updated_count_authors` enum('false','true') DEFAULT 'false' COMMENT 'Пересчитано количество авторов у книги',
`checked_fb2` enum('false','true') DEFAULT 'false' COMMENT 'Проверена книга или фрагмент на метатеги',
`indexed` enum('false','true') DEFAULT 'false' COMMENT 'Проиндексировано ElasticSearch',
`innodb` enum('false','true') DEFAULT 'false',
`index_popular` int(11) DEFAULT NULL COMMENT 'Индекс полпулярности',
`donor_id2` int(11) DEFAULT NULL COMMENT 'дополнительный ID донора',
`isbn` varchar(50) DEFAULT NULL,
`index_popular_set` enum('false','true') DEFAULT 'false' COMMENT 'просчитан индекс популярности',
`storage` tinyint(1) DEFAULT NULL COMMENT 'номер диска для файлов',
`lng` tinyint(4) DEFAULT NULL COMMENT 'язык книги',
`google_search` bigint(20) DEFAULT NULL,
`litres_csv_rate` decimal(5,1) DEFAULT NULL,
`litres_livelib_csv_reads` int(11) DEFAULT NULL,
`livelib_csv_rate` decimal(5,1) DEFAULT NULL,
`livelib_url` varchar(255) DEFAULT NULL,
`livelib_book_id` int(11) DEFAULT NULL COMMENT 'если минус - наш id, если плюс - ЛЛ',
`livelib_share_vk` int(11) DEFAULT NULL,
`livelib_fav_num` int(11) DEFAULT NULL,
`livelib_read_num` int(11) DEFAULT NULL,
`livelib_plan_read` int(11) DEFAULT NULL,
`livelib_comments_num` int(11) DEFAULT NULL,
`livelib_quotes_num` int(11) DEFAULT NULL,
`livelib_collections_num` int(11) DEFAULT NULL,
`master_book` enum('true','false') DEFAULT 'true',
`age` varchar(20) DEFAULT NULL,
`num_pages` varchar(50) DEFAULT NULL,
`old_book_id` int(11) DEFAULT NULL,
`old_book_url` varchar(255) DEFAULT NULL,
`checked_year` enum('false','true') DEFAULT 'false' COMMENT 'проверен ли год выпуска модераторами, чтоб отображать в библиографии',
`checked_pages` enum('false','true') DEFAULT 'false',
PRIMARY KEY (`id`),
KEY `is_status` (`is_status`),
KEY `view_count` (`view_count`),
KEY `poster` (`poster`(255)),
KEY `rating` (`rating`),
KEY `rating_litres` (`rating_litres`),
KEY `date_written` (`date_written`),
KEY `is_description_blocked` (`is_description_blocked`),
KEY `genres_count` (`genres_count`),
KEY `checked_admin` (`checked_admin`),
KEY `no_ru` (`no_ru`),
KEY `count_authors` (`count_authors`),
KEY `updated_count_authors` (`updated_count_authors`),
KEY `checked_fb2` (`checked_fb2`),
KEY `indexed` (`indexed`),
KEY `count_authors_updated_count_authors` (`count_authors`,`updated_count_authors`),
KEY `is_genre_deleted_is_status` (`is_genre_deleted`,`is_status`),
KEY `updated_count_authors_is_status` (`updated_count_authors`,`is_status`),
KEY `innodb` (`innodb`),
KEY `index_popular` (`index_popular`),
KEY `donor_id2` (`donor_id2`),
KEY `year` (`year`),
KEY `index_popular_set` (`index_popular_set`),
KEY `donor` (`donor`),
KEY `donor_id` (`donor_id`),
KEY `google_search` (`google_search`),
KEY `litres_csv_rate` (`litres_csv_rate`),
KEY `litres_livelib_csv_reads` (`litres_livelib_csv_reads`),
KEY `livelib_csv_rate` (`livelib_csv_rate`),
KEY `master_book` (`master_book`),
KEY `livelib_book_id` (`livelib_book_id`),
KEY `storage` (`storage`),
KEY `livelib_read_num` (`livelib_read_num`),
KEY `old_book_id` (`old_book_id`),
KEY `old_book_url` (`old_book_url`),
KEY `checked_year` (`checked_year`),
KEY `is_status_master_book` (`is_status`,`master_book`),
KEY `lng` (`lng`),
KEY `livelib_book_id_master_book` (`livelib_book_id`,`master_book`),
KEY `is_status_master_book_indexed` (`is_status`,`master_book`,`indexed`),
KEY `genres_count_master_book` (`genres_count`,`master_book`),
KEY `count_authors_updated_count_authors_master_book` (`count_authors`,`updated_count_authors`,`master_book`),
KEY `is_status_donor_no_ru_master_book` (`is_status`,`donor`,`no_ru`,`master_book`),
KEY `livelib_url_master_book_is_status` (`livelib_url`,`master_book`,`is_status`),
KEY `donor_donor_id_donor_id2_master_book_is_status` (`donor`,`donor_id`,`donor_id2`,`master_book`,`is_status`),
KEY `lng_is_status_master_book` (`lng`,`is_status`,`master_book`),
KEY `is_status_indexed` (`is_status`,`indexed`),
KEY `books_idx_is_stat_master_livelib_id` (`is_status`,`master_book`,`livelib_read_num`,`id`),
KEY `books_idx_livelib_num_id` (`livelib_read_num`,`id`),
KEY `master_book_is_status` (`master_book`,`is_status`),
KEY `is_status_master_book_year` (`is_status`,`master_book`,`year`),
FULLTEXT KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `books_genres` (
`book_id` int(11) NOT NULL,
`genre_id` int(11) NOT NULL,
`sort` tinyint(4) DEFAULT NULL,
PRIMARY KEY (`book_id`,`genre_id`),
UNIQUE KEY `book_id` (`book_id`,`genre_id`),
KEY `categorie_id` (`genre_id`),
KEY `sort` (`sort`),
KEY `book_id2` (`book_id`),
KEY `genre_id_book_id` (`genre_id`,`book_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8';
更新1(它仍然很長):
select sql_no_cache
count(`books`.`id`) as `count`
from `books`
use index(is_status_master_book)
where
(
1 = 1
AND `books`.`is_status` = 'active'
AND `books`.`master_book` = 'true'
)
AND (
EXISTS (
SELECT
1
FROM
`books_genres`
WHERE
(
`books_genres`.`book_id` = `books`.`id`
)
AND (
`books_genres`.`genre_id` IN (307,380,385,384,1359,381,390,397,394,949,1390,1391,403,401,1332,393,398,1374,1397,402,984,1025,841,1027,359,577,365,1021,1023,360,368,369,370,942,1061,1348,1376,437,737,1137,1354,1384,1385,1115,1113,1114,1143,1363,593,581,583,567,978,973,576,677,825,595,826,1043,827,1077,323,324,1361,1362,1360,407,610,611,1179,608,336,831,1042,520,1079,1078,1081,352,1349,388,727,728,729,325,330,1099,616,320,1375,1138,1388,1119,1141,1140,1328,1136,1044,1103,1074,1150,1322)
)
)
)
結果:
+--------+
| count |
+--------+
| 402545 |
+--------+
1 row (1.774 s)
解釋:
+------+--------------+--------------+--------+--------------------------------------------------------+-----------------------+---------+-------------+---------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+--------------+--------------+--------+--------------------------------------------------------+-----------------------+---------+-------------+---------+--------------------------+
| 1 | PRIMARY | books | ref | is_status_master_book | is_status_master_book | 3 | const,const | 975330 | Using where; Using index |
| 1 | PRIMARY | <subquery2> | eq_ref | distinct_key | distinct_key | 4 | func | 1 | |
| 2 | MATERIALIZED | books_genres | index | PRIMARY,book_id,categorie_id,book_id2,genre_id_book_id | PRIMARY | 8 | NULL | 1866103 | Using where; Using index |
+------+--------------+--------------+--------+--------------------------------------------------------+-----------------------+---------+-------------+---------+--------------------------+
3 rows in set (0.00 sec)
更新 2:
select sql_no_cache
count(1) as `count`
from `books`
use index(is_status_master_book_id)
where
(
1 = 1
AND `books`.`is_status` = 'active'
AND `books`.`master_book` = 'true'
)
AND (
EXISTS (
SELECT
1
FROM
`books_genres`
WHERE
(
`books_genres`.`book_id` = `books`.`id`
)
AND (
`books_genres`.`genre_id` IN (307,380,385,384,1359,381,390,397,394,949,1390,1391,403,401,1332,393,398,1374,1397,402,984,1025,841,1027,359,577,365,1021,1023,360,368,369,370,942,1061,1348,1376,437,737,1137,1354,1384,1385,1115,1113,1114,1143,1363,593,581,583,567,978,973,576,677,825,595,826,1043,827,1077,323,324,1361,1362,1360,407,610,611,1179,608,336,831,1042,520,1079,1078,1081,352,1349,388,727,728,729,325,330,1099,616,320,1375,1138,1388,1119,1141,1140,1328,1136,1044,1103,1074,1150,1322)
)
)
)
結果:
+--------+
| count |
+--------+
| 402553 |
+--------+
1 row in set (3.84 sec)
更新 3:
ALTER TABLE books ADD INDEX is_status_master_book_id (is_status, master_book, id);
SELECT sql_no_cache COUNT(id) AS `count`
FROM books
use index(is_status_master_book_id)
WHERE books.is_status = 'active'
AND books.master_book = 'true'
AND id IN (
SELECT book_id
FROM books_genres
WHERE genre_id IN (307,380,385,384,1359,381,390,397,394,949,1390,1391,403,401,1332,393,398,1374,1397,402,984,1025,841,1027,359,577,365,1021,1023,360,368,369,370,942,1061,1348,1376,437,737,1137,1354,1384,1385,1115,1113,1114,1143,1363,593,581,583,567,978,973,576,677,825,595,826,1043,827,1077,323,324,1361,1362,1360,407,610,611,1179,608,336,831,1042,520,1079,1078,1081,352,1349,388,727,728,729,325,330,1099,616,320,1375,1138,1388,1119,1141,1140,1328,1136,1044,1103,1074,1150,1322)
)
結果:
+--------+
| count |
+--------+
| 402553 |
+--------+
1 row in set (1.47 sec)
首先,可以肯定的是,使用ANALYZE TABLE更新 MariaDB 使用的內部統計信息,以確定要使用哪些索引。
ANALYZE TABLE books, book_genres;
在 MariaDB 中,您可以在查詢中使用ANALYZE SELECT
代替EXPLAIN SELECT
。 ANALYZE
實際上運行查詢,然后顯示與EXPLAIN
相同類型的輸出,但具有更多詳細信息。 所以,用它來做你的分析。
SELECT COUNT()
,遺憾的是,在大表上本質上很慢。 如果你這樣做
SELECT COUNT(id) AS `count`
FROM books
WHERE books.is_status = 'active'
AND books.master_book = 'true'
您將獲得查詢所需時間的(最小值)下限。 您的名為is_status_master_book
索引將有助於此查詢,除非您的大部分書籍都是“活動”和“主”,在這種情況下,服務器會選擇表掃描,因為索引的選擇性不夠。 使此查詢正常工作並研究 ANALYZE 輸出。 (InnoDB 索引總是將主鍵附加到列列表的末尾。)
(請注意,您的is_status
指數是多余的與你is_status_master_book
指數。)
接下來,處理這個更短版本的流派查找。
SELECT book_id
FROM books_genres
WHERE genre_id in (307,380,385,384,1359,381,390,397,394)
這個查詢應該使用genre_id_book_id
您books_genres
表。 它有效嗎? 它的表現是否令人滿意? 如果是這樣,請使用所有的流派 ID 再試一次。
如果它的性能在更長的列表中明顯變差,您可以嘗試重構您的應用程序以將該長列表放入一個臨時表中並執行
SELECT book_id
FROM books_genres
JOIN temptable ON books_genres.genre_id = temptable.genre_id
下一步,試試這個。
SELECT COUNT(id) AS `count`
FROM books
WHERE books.is_status = 'active'
AND books.master_book = 'true'
AND id IN (
SELECT book_id
FROM books_genres
WHERE genre_id IN ( short list )
)
這表現如何? 查看 ANALYZE 輸出。 嘗試使用不同的索引; 如果id
比is_status
和master_book
更有選擇性,它可能會有所幫助。
ALTER TABLE books ADD INDEX id_status_master (id, is_status, master);
然后在上述查詢中嘗試更長的流派列表。 該查詢相當於您的查詢。
你有一些多余的索引,我會先去掉那些......不是說它們會對查詢產生影響,但我會解釋。
如果您在 (a, b) 上有另一個索引 (b, a) 上的索引,並且您運行一個查詢限定 BOTH 列,那么任何一個都可以工作。 但是,如果您只需要 b 列作為查詢的一部分,請在其上建立索引。 如果查詢列 a + 其他任何內容,也將使用 (a, b)。
所以你的書表索引
is_status (is_status) is_status_master_book (is_status, master_book)
單個“is_status”索引是多余的。 通過查看其他一些索引,可能會有更好的索引選項,但在不知道查詢上下文的情況下,我將不理會。
現在,為了更上一層樓,對於這個特定的查詢,您可能需要創建一個 COVERING 索引。 這基本上意味着索引將查詢請求的所有列作為索引的一部分,因此引擎永遠不需要去記錄數據的各個原始頁面來獲取/限定結果。
因此,我會將您的“Is_Status_Master_Book”索引更改為
is_status_master_book (is_status, master_book, id)
同樣,在您的書籍類型表中,您的唯一鍵“Book_id”具有書籍 ID 和類型。 但也使“genre_id_book_id”變得多余,但如果曾經執行過僅genre_id查詢,則可以保留categrie_id索引作為覆蓋genre_id的索引。
現在,對於您的查詢。 您有一個左連接,但在 where 子句中有一個“AND”Genre_id,使其成為 INNER JOIN。
select sql_no_cache
count( distinct b.id ) as `count`
from
books b
join books_genres bg
on b.id = bg.book_id
where
1
and b.is_status = 'active'
and b.master_book = 'true'
and bg.genre_id in (307,380,385,384,1359,381,390,397,394,949,1390,1391,403,401,1332,393,
398,1374,1397,402,984,1025,841,1027,359,577,365,1021,1023,360,368,369,
370,942,1061,1348,1376,437,737,1137,1354,1384,1385,1115,1113,1114,1143,
1363,593,581,583,567,978,973,576,677,825,595,826,1043,827,1077,323,324,
1361,1362,1360,407,610,611,1179,608,336,831,1042,520,1079,1078,1081,352,
1349,388,727,728,729,325,330,1099,616,320,1375,1138,1388,1119,1141,1140,
1328,1136,1044,1103,1074,1150,1322)
您的查詢的真正意圖是什么...您是否想要對屬於主書籍且符合所列類型之一的所有活動狀態書籍進行計數? 如果是這樣,那就是您的查詢(減去左連接部分),但按照建議更新索引 is_status_master_book 以使其覆蓋,因此不需要轉到原始數據頁面來限定記錄並返回結果。
另一種方法是預先查詢符合條件的類型中的不同書籍。
select sql_no_cache
count( distinct b.id ) as `count`
from
books b
join
( select distinct bg.book_id
from books_genres bg
where bg.genre_id in (307,380,385,384,1359,381,390,397,394,949,1390,1391,403,401,1332,393,
398,1374,1397,402,984,1025,841,1027,359,577,365,1021,1023,360,368,369,
370,942,1061,1348,1376,437,737,1137,1354,1384,1385,1115,1113,1114,1143,
1363,593,581,583,567,978,973,576,677,825,595,826,1043,827,1077,323,324,
1361,1362,1360,407,610,611,1179,608,336,831,1042,520,1079,1078,1081,352,
1349,388,727,728,729,325,330,1099,616,320,1375,1138,1388,1119,1141,1140,
1328,1136,1044,1103,1074,1150,1322) ) PQ
on b.id = PQ.book_id
where
1
and b.is_status = 'active'
and b.master_book = 'true'
交付 40 萬行需要時間。
簡化索引:
PRIMARY KEY (`book_id`,`genre_id`),
UNIQUE KEY `book_id` (`book_id`,`genre_id`), -- redundant
KEY `categorie_id` (`genre_id`), -- in the way
KEY `sort` (`sort`),
KEY `book_id2` (`book_id`), -- in the way
KEY `genre_id_book_id` (`genre_id`,`book_id`)
-->
PRIMARY KEY (`book_id`,`genre_id`),
KEY `sort` (`sort`),
KEY `genre_id_book_id` (`genre_id`,`book_id`)
當您有INDEX(a,b)
,您也不需要INDEX(a)
。
並擺脫use index(categorie_id)
。
除非表的重點是列出所有x
否則將x
和x_id
放在同一個表中似乎很奇怪。 (我指的是donor
。)
查詢是受 I/O 限制還是受 CPU 限制?
你有多少內存? innodb_buffer_pool_size
的值是innodb_buffer_pool_size
? 每個表有多少行? 是磁盤HDD還是SSD?
如果它受 I/O 限制,則有多種可能的加速。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.