MariaDB Optimization and Indexes 中文版

Posted ACMUG

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MariaDB Optimization and Indexes 中文版相关的知识,希望对你有一定的参考价值。



作者: MariaDB官方授权资词翻译组翻译

资词翻译组,由数名数据库技术爱好者组成,旨在传播技术,帮助他人,提升自我。目前的成员有:田丽芳,强昌金,王竹峰,侯军伟,吕智超,刘启荣,周彦伟。
译事三难:信、达、雅。信者,真也,真者,不伪也;达者,至也,至者,无过无不及也;雅者,文学性也,文学性者,当雅则雅当俗则俗也。我们深知能达成此三事,绝非一日之功,亦非常人所能。然,苟利国家生死以,岂因祸福避趋之。我们还是希望竭尽所能,不遗余力,做一点微小的工作,提高姿势水平。由于能力有限,水平一般,有错误不妥之处,还请批评指正,希望能得到大家的资词。

资词翻译组获得mysql Server团队和MariaDB官方授权,翻译相关技术文章。


什么是索引?

试想你已经有了如下的一张表(该表与 More Advanced Joins (https://mariadb.com/kb/en/more-advanced-joins/)教程中使用的表相同)。

+----+------------+-----------+-------------------------+---------------------------+--------------+
| ID | First_Name | Last_Name | Position                | Home_Address              | Home_Phone   |
+----+------------+-----------+-------------------------+---------------------------+--------------+
|  1 | Mustapha   | Mond      | Chief Executive Officer | 692 Promiscuous Plaza     | 326-555-3492 |
|  2 | Henry      | Foster    | Store Manager           | 314 Savage Circle         | 326-555-3847 |
|  3 | Bernard    | Marx      | Cashier                 | 1240 Ambient Avenue       | 326-555-8456 |
|  4 | Lenina     | Crowne    | Cashier                 | 281 Bumblepuppy Boulevard | 328-555-2349 |
|  5 | Fanny      | Crowne    | Restocker               | 1023 Bokanovsky Lane      | 326-555-6329 |
|  6 | Helmholtz  | Watson    | Janitor                 | 944 Soma Court            | 329-555-2478 |
+----+------------+-----------+-------------------------+---------------------------+--------------+

解决这个问题的方法是给记录排序。如果记录按last name列的字母顺序排序存储的话,即使普通的人类也可以在大量数据中很快的找到一条记录。但是我们不能靠last name来排序全部记录。如果我们想要按照ID检索纪录,或者按照first name检索呢?答案就是为我们希望进行排序的每一列都创建单独的索引。一个索引只是很简单的包含了已经被排序了的数据(比如surname)和对应指向原始纪录的指针。

例如,一个基于Last_Name字段的索引:

+-----------+----+
| Last_Name | ID |
+-----------+----+
| Crowne    |  4 |
| Crowne    |  5 |
| Foster    |  2 |
| Marx      |  3 |
| Mond      |  1 |
| Watson    |  6 |
+-----------+----+

和一个基于Position字段的索引

+-------------------------+----+
| Position                | ID |
+-------------------------+----+
| Cashier                 |  3 |
| Cashier                 |  4 |
| Chief Executive Officer |  1 |
| Janitor                 |  6 |
| Restocker               |  5 |
| Store Manager           |  2 |
+-------------------------+----+

索引入门

索引的类型主要有四种:主键(索引),唯一索引,一般索引和全文索引
术语‘KEY’和‘INDEX’一般可以通用,语句可以使用两个关键字的任意一个。

主键

主键是唯一且永不能为空(null)的。主键总是指向一条且仅一条纪录,并且每条记录都必须被代表(each record must be represented)。每个表只能有一个主键列。
在 XtraDB/InnoDB (https://mariadb.com/kb/en/mariadb/xtradb-and-innodb/)表中,所有的索引都含有主键作为后缀。因此,在使用这个存储引擎时,保持主键尽可能的小是尤其重要的。如果一个主键列不存在,同时也没有唯一索引列,InnoDB会创建一个用户不可见的6个字节大小的聚簇索引。
很多表都采用一个数字ID列作为主键。
主键通常是在使用 CREATE TABLE (https://mariadb.com/kb/en/create-table/#indexes)语句建表时被添加的。例如,下例就是在ID列上建了一个主键。注意ID列必须要定义为NOT NULL,否则索引就不能被创建。

CREATE TABLE `Employees` (
  `ID` TINYINT(3) UNSIGNED NOT NULL AUTO_INCREMENT,
  `First_Name` VARCHAR(25) NOT NULL,
  `Last_Name` VARCHAR(25) NOT NULL,
  `Position` VARCHAR(25) NOT NULL,
  `Home_Address` VARCHAR(50) NOT NULL,
  `Home_Phone` VARCHAR(12) NOT NULL,
  PRIMARY KEY (`ID`)
) ENGINE=Aria;

你不能使用 CREATE INDEX (https://mariadb.com/kb/en/mariadb/create-index/)命令创建主键。如果你想要在表被建立后添加一个主键的话,可以使用 ALTER TABLE (https://mariadb.com/kb/en/alter-table/),例如:

ALTER TABLE Employees ADD PRIMARY KEY(ID);

唯一索引

一个唯一索引必须是唯一的,但是它可以为null。故每个索引的值只标识一条记录,但并不是每条记录都需要被代表(not each record needs to be represented)。

例如,可以像创建主键索引那样在Employee_Code列上创建一个唯一索引:

CREATE TABLE `Employees` (
  `ID` TINYINT(3) UNSIGNED NOT NULL,
  `First_Name` VARCHAR(25) NOT NULL,
  `Last_Name` VARCHAR(25) NOT NULL,
  `Position` VARCHAR(25) NOT NULL,
  `Home_Address` VARCHAR(50) NOT NULL,
  `Home_Phone` VARCHAR(12) NOT NULL,
  `Employee_Code` VARCHAR(25) NOT NULL,
  PRIMARY KEY (`ID`),
  UNIQUE KEY (`Employee_Code`)
) ENGINE=Aria;

唯一索引也可以在一个表被创建后使用CREATE INDEX或ALTER TABLE命令来添加,例如:

ALTER TABLE Employees ADD UNIQUE `EmpCode`(`Employee_Code`);

CREATE UNIQUE INDEX HomePhone ON Employees(Home_Phone);

事实上UNIQUE约束可以为NULL经常会被忽略掉。在SQL中任何NULL都从不等于任何东西,即使是与其他的NULL也不相等。因此,UNIQUE约束并不妨碍存储含有null值的重复行:

CREATE TABLE Table_1 (a int not null, b int, unique (a,b));
insert into Table_1 values (1,1),(2,NULL),(2,NULL);
select * from Table_1;

+---+------+
| a | b    |
+---+------+
| 1 |    1 |
| 2 | NULL |
| 2 | NULL |
+---+------+

的确是这样的,在SQL的最后两行中,即使它们看起来一样,但彼此间并不相等:

SELECT (2, NULL) = (2, NULL);

+---------------------- +
| (2, NULL) = (2, NULL) |
+---------------------- +
| 0                     |
+---------------------- +

在MariaDB中,你可以将该特征和 virtual columns (https://mariadb.com/kb/en/virtual-columns/)结合起来以强制使表中诸多行的一个子集满足唯一性:

create table Table_1 (
  user_name varchar(10),
  status enum(**'Active'**, **'On-Hold'**, **'Deleted'**),
  del char(0) as (if(status in (**'Active'**, **'On-Hold'**),**''**, NULL)) persistent,
  unique(user_name,del)
)

这个表结构确保所有activeon-hold用户能有不同的名字,但是只要有用户被删除,他的名字就不再是唯一性约束的一部分,其他用户就可以使用这个相同的名字了。
如果一个唯一索引存在于一个后面的填充字符被去除或忽略的列中,向这个列插入只有后面填充字符不同的数据时会导致一个duplicate-key错误。

联合索引

索引可以包含不止一个的列。即使不能使用整个索引,MariaDB也可以使用在索引最左侧部分上的一个或多个列。

索引的选择

一般情况下你应该只添加与你的应用的query 相匹配的索引。任何额外的索引都会浪费资源。在一个有着很小的表的应用中,索引不会造成什么差别,但一旦你的表大小大于buffer size,索引就会开始发挥很大的加速效用。

在你的query中使用 EXPLAIN (https://mariadb.com/kb/en/mariadb/explain/)语句可以帮助你决定那个列需要索引。
如果你的query中含有类似LIKE '%word%'这样的语句,如果没有全文索引,那么其实你每次使用的都是全表扫描,很慢的全表扫描。
如果你的表有大量的读和写,考虑使用延迟写。这种方式以一种”批处理”写模式来使用数据库引擎,这样就可以减少磁盘IO,故而提高了性能。
使用CREATE INDEX命令创建索引。
如果你在构建一张很大的表的话,那么为了最好的性能请在表被填充数据之后再添加索引。这样做可以提高插入性能,消除插入时的索引开销。

查看索引

你可以用 SHOW INDEX (https://mariadb.com/kb/en/show-index/)来查看一个表中有哪些索引以及这些索引的细节。
如果你想知道如何重建一个索引,运行SHOW CREATE TABLE  。

何时移除一个索引

如果一个索引很少被使用(或者根本不被使用),那么应删除该索引以提高INSERT和UPDATE操作的性能。


如果 user statistics (https://mariadb.com/kb/en/user-statistics/)已经被开启, Information Schema (https://mariadb.com/kb/en/information-schema/)的 INDEX_STATISTICS (https://mariadb.com/kb/en/mariadb/information-schema-index_statistics-table/)表会存储索引使用信息。

如果 慢查询日志 (https://mariadb.com/kb/en/mariadb/slow-query-log/)被开启并且 log_queries_not_using_indexes (https://mariadb.com/kb/en/server-system-variables/#log_queries_not_using_indexes)系统参数设为`ON`,没有使用到索引query就会被纪录。