mariadb - join/order by 子句导致查询缓慢

mariadb - join/order by clauses cause slow query

有一个小型博客站点,主机提供商运行 Server version: 5.5.5-10.3.34-MariaDB-cll-lve MariaDB Server。最近几周我注意到获取帖子的速度变慢了,结果查询现在需要 6 秒。

有问题的查询:

SELECT * FROM POSTS
JOIN POST_CATEGORIES on POST_CATEGORIES.CATEG_ID = POSTS.POST_CATEGORY
LEFT JOIN USERS on POSTS.POST_USERNAME = USERS.USER_USERNAME
WHERE POST_STATUS = 'ENA'
AND POST_IS_PUBLIC = 'Y'
ORDER BY POST_IS_STICKY DESC, (TIMESTAMPDIFF(SECOND, POST_CREATION_DATE, now())-(POST_LIKES-POST_DISLIKES)*3600) ASC
LIMIT 0, 12;

如果我删除 order by,查询时间将下降到 0.04 秒,但显然它是正确排序所必需的。如果我删除 USERS 的左连接,也会发生同样的效果,但我想关注 order by 子句。

Table 行:

POSTS: 62K
POST_CATEGORIES: 20
USERS: 180

有趣的发现:

table 上已经有索引(primary/unique/foreign 键),但涉及的列上没有,为简单起见,我不在此处添加它们。

但我确实尝试添加索引:

INDEX `POSTS_OPTIMIZING_INDEX` (`POST_CREATION_DATE`, `POST_LIKES`, `POST_DISLIKES`) USING BTREE,

FORCE INDEX (POSTS_OPTIMIZING_INDEX) 看到了一些有趣的东西:

关于优化 SQL 或调整 mariadb 有什么建议吗?

编辑

table定义:

帖子数:

CREATE TABLE `POSTS` (
  `POST_ID` int(11) NOT NULL AUTO_INCREMENT,
  `POST_URL_ID` varchar(30) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `POST_TYPE` varchar(3) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `POST_CATEGORY` int(3) DEFAULT NULL,
  `POST_IS_PUBLIC` varchar(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `POST_IS_STICKY` varchar(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `POST_FREE_TEXT` varchar(5000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `POST_IMAGE_FILE` varchar(1000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `POST_VIDEO_URL` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `POST_IS_FROM_USER` varchar(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `POST_USERNAME` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `POST_CREATION_DATE` timestamp NOT NULL DEFAULT current_timestamp(),
  `POST_STATUS` varchar(3) COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'ENA',
  `POST_LIKES` int(5) DEFAULT 0,
  `POST_DISLIKES` int(5) DEFAULT 0,
  `POST_ADDITIONAL_INFO` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `POST_FB_OBJ_ID` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `POST_FB_OBJ_TYPE` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `POST_FB_IMG_LAST_CHECKED` timestamp NULL DEFAULT NULL,
  `POST_FB_IMG_LAST_CHECK_RESULT` varchar(3) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `POST_TWITTER_OBJ_ID` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`POST_ID`),
  UNIQUE KEY `POST_URL_ID` (`POST_URL_ID`),
  KEY `FK_POSTS_POST_CATEGORIES` (`POST_CATEGORY`),
  CONSTRAINT `FK_POSTS_POST_CATEGORIES` FOREIGN KEY (`POST_CATEGORY`) REFERENCES `POST_CATEGORIES` (`CATEG_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=62529 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci

POST_CATEGORIES:

CREATE TABLE `POST_CATEGORIES` (
  `CATEG_ID` int(11) NOT NULL AUTO_INCREMENT,
  `CATEG_URL_NAME` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  `CATEG_NAME` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`CATEG_ID`)
) ENGINE=InnoDB AUTO_INCREMENT=51 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

用户:

CREATE TABLE `USERS` (
  `USER_ID` int(11) NOT NULL AUTO_INCREMENT,
  `USER_MAIL_ADDRESS` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_USERNAME` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_PASSWORD` varchar(60) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_CREATION_DATE` timestamp NULL DEFAULT current_timestamp(),
  `USER_LAST_LOGIN_DATE` timestamp NULL DEFAULT NULL,
  `USER_FROM_FB` varchar(1) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_FB_MAIL_ADDRESS` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_FB_FULL_NAME` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_SPECIAL` varchar(1) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_ACCT_STATUS` varchar(3) COLLATE utf8_unicode_ci DEFAULT 'ENA',
  `USER_POSTS_COUNT` int(5) DEFAULT 0,
  `USER_NAME` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_QUOTE` varchar(200) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_ABOUT_MYSELF` varchar(500) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_WEBSITE` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_FB` varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_TWITTER` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_LOCATION` varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_BIRTHDAY` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_PROFILE_PIC` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL,
  `USER_PROFILE_PIC_FROM_FB` varchar(1) COLLATE utf8_unicode_ci DEFAULT NULL,
  PRIMARY KEY (`USER_ID`),
  UNIQUE KEY `USER_USERNAME` (`USER_USERNAME`)
) ENGINE=InnoDB AUTO_INCREMENT=188 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

EXPLAIN:

mysql> EXPLAIN
    -> SELECT * FROM POSTS
    -> JOIN POST_CATEGORIES on POST_CATEGORIES.CATEG_ID = POSTS.POST_CATEGORY
    -> LEFT JOIN USERS on POSTS.POST_USERNAME = USERS.USER_USERNAME
    -> WHERE POST_STATUS = 'ENA'
    -> AND POST_IS_PUBLIC = 'Y'
    -> ORDER BY POST_IS_STICKY DESC, (TIMESTAMPDIFF(SECOND, POST_CREATION_DATE, now())-(POST_LIKES-POST_DISLIKES)*3600) ASC
    -> LIMIT 0, 12;
+------+-------------+-----------------+--------+--------------------------+---------+---------+--------------------------------+-------+-------------------------------------------------+
| id   | select_type | table           | type   | possible_keys            | key     | key_len | ref                            | rows  | Extra                                           |
+------+-------------+-----------------+--------+--------------------------+---------+---------+--------------------------------+-------+-------------------------------------------------+
|    1 | SIMPLE      | POSTS           | ALL    | FK_POSTS_POST_CATEGORIES | NULL    | NULL    | NULL                           | 61407 | Using where; Using temporary; Using filesort    |
|    1 | SIMPLE      | POST_CATEGORIES | eq_ref | PRIMARY                  | PRIMARY | 4       | MY_DB.POSTS.POST_CATEGORY |     1 |                                                 |
|    1 | SIMPLE      | USERS           | ALL    | NULL                     | NULL    | NULL    | NULL                           |   178 | Using where; Using join buffer (flat, BNL join) |
+------+-------------+-----------------+--------+--------------------------+---------+---------+--------------------------------+-------+-------------------------------------------------+
3 rows in set (0.03 sec)

mysql>  

并删除 2 个连接:

mysql> EXPLAIN
    -> SELECT * FROM POSTS
    -> WHERE POST_STATUS = 'ENA'
    -> AND POST_IS_PUBLIC = 'Y'
    -> ORDER BY POST_IS_STICKY DESC, (TIMESTAMPDIFF(SECOND, POST_CREATION_DATE, now())-(POST_LIKES-POST_DISLIKES)*3600) ASC
    -> LIMIT 0, 12;
+------+-------------+-------+------+---------------+------+---------+------+-------+-----------------------------+
| id   | select_type | table | type | possible_keys | key  | key_len | ref  | rows  | Extra                       |
+------+-------------+-------+------+---------------+------+---------+------+-------+-----------------------------+
|    1 | SIMPLE      | POSTS | ALL  | NULL          | NULL | NULL    | NULL | 61407 | Using where; Using filesort |
+------+-------------+-------+------+---------------+------+---------+------+-------+-----------------------------+
1 row in set (0.02 sec)

mysql> 

TIA

"Explode-implode" 综合症。重写查询以将 12 行 firstthen JOIN 到其他表。

SELECT ...
    FROM ( SELECT user_id,
                  is_sticky,
                  (TIMESTAMPDIFF(SECOND, POST_CREATION_DATE, now()) -
                     (POST_LIKES-POST_DISLIKES)*3600)  AS  metric
             FROM POSTS
             WHERE  POST_STATUS = 'ENA'
               AND  POST_IS_PUBLIC = 'Y'
             ORDER BY
                 POST_IS_STICKY DESC, metric ASC
             LIMIT  0, 12
         ) AS x
     JOIN POSTS AS p USING(user_id)
     JOIN  POST_CATEGORIES AS pc  ON pc.CATEG_ID = p.POST_CATEGORY
     LEFT JOIN  USERS AS u  ON p.POST_USERNAME = u.USER_USERNAME
     ORDER BY x.POST_IS_STICKY DESC, x.metric ASC

将省略号替换为您真正需要的列。

是的,POSTS 被第二次查看,但仅查看 12 行,并且由 PK。同样,其他行也只达到了12次。

是的,ORDER BY 需要重复。如果 JOIN 创建了额外的行(例如,多个类别),则 LIMIT 可能需要重复。

考虑使用

SELECT ...
       ( SELECT GROUP_CONCAT(CATEG_NAME) FROM POST_CATEGORIES
              WHERE categ_id = p.post_categories ) AS Categories,
       ...

而不是加入 POST_CATEGORIES,尤其是在有多个类别的情况下。

可能有助于提高性能,但前提是它有足够的选择性:

INDEX(post_tatus, post_is_public)