自从诞生以来,MariaDb(MySQL)索引有一个限制:它们不能降序(create index desc)。是的,ALTER TABLE和CREATE INDEX可以包含ASC和DESC关键字,这在MySQL中完全合法,但这并不意味着下降索引被创建。也就是Mysql支持索引desc这样的语法,但是实际上并不支持这样的功能,比如:order by column _name desc并不能使用索引。这是一个众所周知的MySQL功能:当它不能做某事时,它只是假装。嗯…有人认为这是一个功能,但我认为这是一个BUG(一个错误是一个意想不到的行为),那我们能做什么呢?
缺少对降序索引的支持只是一个问题,当我们需要创建一个索引,其中至少有一列升序,并且至少有一列是降序。例如,MySQL假装理解以下语句,但是生成的索引不太可能有助于我们:
CREATE INDEX my_index ON my_table (my_column ASC, your_column DESC);
ORDER BY my_column ASC,your_column DESC将无法使用此索引。
MariaDB有一个功能,为缺少降序索引提供了一个很好的解决方法:PERSISTENT列。如果您不了解它们,请先查看MariaDB KnowledgeBase。
PERSISTENT列如何帮助我们创建混合order索引?如果你还没有猜到,我会告诉你的。假设我们有一列A,我们希望它成为ASC + DESC索引的一部分。我们需要创建一个PERSISTENT列rev_A,其值是根据A中的值计算的。这里重要的是rev_A值的顺序与A是相反的。这是一个例子:
CREATE TABLE `exam_scores`
(
`id` INT SIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY
, `student_name` VARCHAR(200) NOT NULL
, `exam_code` CHAR(1)
, `exam_score` SMALLINT UNSIGNED NOT NULL
, `exam_date` DATETIME NOT NULL
-- auxiliary columns
, `rev_exam_code` TINYINT UNSIGNED AS (255 - ORD(exam_code)) PERSISTENT
, `rev_exam_score` SMALLINT SIGNED AS (0 - exam_score) PERSISTENT
, `rev_exam_date` SMALLINT UNSIGNED AS (DATEDIFF('2050-01-01', exam_date)) PERSISTENT
-- mixed indexes
, INDEX `desc_exam_code` (`rev_exam_code`, `student_name`, `exam_code`)
, INDEX `desc_exam_score` (`rev_exam_score`, `student_name`)
, INDEX `desc_exam_date` (`rev_exam_date`, `student_name`, `exam_date`)
)
ENGINE = InnoDB
DEFAULT CHARACTER SET = 'utf8'
;
-- this is needed because some rev* columns return negative values
SET @@global.sql_mode := CONCAT(@@global.sql_mode, ',NO_UNSIGNED_SUBTRACTION');
SET @@session.sql_mode := @@global.sql_mode;
INSERT INTO `exam_scores`
(`student_name`, `exam_code`, `exam_score`, `exam_date`)
VALUES
('Anthony Stark', 'F', 100, '1994-01-01')
, ('Klark Kent', 'G', 60, '2000-01-01')
, ('Corto Maltese', 'A', 50, '2007-01-01')
, ('Tin Tin', 'Z', 900, '2005-01-01')
, ('Philip Mortimer', 'C', 20, '2000-01-01')
, ('Dago', 'M', 80, '2004-01-01')
;
是的,这张表设计得很烂。你想说它不是好的关系表等等,但是我们在谈论在MariaDB中的混合order索引,而不是关系理论。
现在很清楚吗?但愿如此。我们来看一个示例查询。我们想列出学生:最近的考试,如果两个日期相同,按字母排序。
MariaDB [test]> EXPLAIN SELECT `student_name`, `exam_date`
-> FROM `exam_scores`
-> ORDER BY `rev_exam_date` ASC, `student_name` ASC;
+------+-------------+-------------+-------+---------------+----------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------------+-------+---------------+----------------+---------+------+------+-------------+
| 1 | SIMPLE | exam_scores | index | NULL | desc_exam_date | 613 | NULL | 7 | Using index |
+------+-------------+-------------+-------+---------------+----------------+---------+------+------+-------------+
1 row in set (0.00 sec)
desc_exam_score索引表明:在索引中包含原始列并不是必须的:
MariaDB [test]> EXPLAIN SELECT `student_name`, -`rev_exam_score` FROM `exam_scores` ORDER BY `rev_exam_score` ASC, `student_name` ASC;
+------+-------------+-------------+-------+---------------+-----------------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------------+-------+---------------+-----------------+---------+------+------+-------------+
| 1 | SIMPLE | exam_scores | index | NULL | desc_exam_score | 605 | NULL | 6 | Using index |
+------+-------------+-------------+-------+---------------+-----------------+---------+------+------+-------------+
1 row in set (0.00 sec)
-column_name 显然是一个快速的获得真实值的操作。
示例表格显示了如何为数字,日期和ASCII 1字符串创建一个反向的索引部分。多字符ASCII字符串也很简单。我不知道如何创建一个反转的Unicode列。如果找到方法,请在文章下面留下评论。
本文翻译自:https://falseisnotnull.wordpress.com/2014/04/25/descending-indexes-in-mariadb/ |