MySQL索引


img

MySQL索引

1、使用索引的必要性

索引:提高数据库的性能,索引是物美价廉的东西了。不用加内存,不用改程序,不用调sql,只要执行正确的create index语句 ,查询速度就可能提高成百上千倍。但是天下没有免费的午餐,查询速度的提高是以插入、更新、删除的速度为代价的,这些写操作,增加了大量的IO。所以它的价值,在于提高一个海量数据的检索速度

常见索引分为:

  • 主键索引(primary key)

  • 唯一索引(unique)

  • 普通索引(index)

  • 全文索引(fulltext) – 解决中子文索引问题

案例:先创建一个表并插入海量数据,在查询的时候,看看没有索引时有什么问题。

DELIMITER $$

CREATE FUNCTION rand_string(n INT)
RETURNS VARCHAR(255)
BEGIN
  DECLARE chars_str VARCHAR(100) DEFAULT 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
  DECLARE return_str VARCHAR(255) DEFAULT '';
  DECLARE i INT DEFAULT 0;

  WHILE i < n DO
      SET return_str = CONCAT(return_str, SUBSTRING(chars_str, FLOOR(1 + RAND() * 52), 1));
      SET i = i + 1;
  END WHILE;

  RETURN return_str;
END $$

DELIMITER ;

-- 产生随机数字
DELIMITER $$

CREATE FUNCTION rand_num()
RETURNS INT(5)
BEGIN
  DECLARE i INT DEFAULT 0;
  SET i = FLOOR(10 + RAND() * 500);
  RETURN i;
END $$

DELIMITER ;

-- 创建存储过程,向雇员表添加海量数据
DELIMITER $$

CREATE PROCEDURE insert_emp(IN start INT(10), IN max_num INT(10))
BEGIN
  DECLARE i INT DEFAULT 0;
  SET autocommit = 0;

  REPEAT
      SET i = i + 1;
      INSERT INTO EMP VALUES ((start + i), rand_string(6), 'SALESMAN', 0001, CURDATE(), 2000, 400, rand_num());
  UNTIL i = max_num
  END REPEAT;

  COMMIT;
END $$

DELIMITER ;

-- 执行存储过程,添加8000000条记录
CALL insert_emp(100001, 8000000);

到此,已经创建出了海量数据的表了。

查看有多少条数据:

mysql> select count(*) from EMP;
+----------+
| count(*) |
+----------+
|  8000013 |
+----------+
1 row in set (1.57 sec)

mysql>
  • 查询名字为smith的员工。
mysql> select * from EMP where ename='smith';
+-------+-------+-------+------+------------+--------+------+--------+
| EMPNO | ENAME | JOB   | MGR  | HIREDATE   | SAL    | COMM | DEPTNO |
+-------+-------+-------+------+------------+--------+------+--------+
|  7369 | SMITH | CLERK | 7902 | 1980-12-17 | 800.00 | NULL |     20 |
+-------+-------+-------+------+------------+--------+------+--------+
1 row in set (2.84 sec)

发现耗时2.84秒。这还是在本机一个人来操作,在实际项目中,如果放在公网中,假如同时有1000个人并发查询,那很可能就死机。(这里如果是查询编号很大的员工,可能查询会很快,因为员工号是主见,默认创建了主键索引,下面会讲)

  • 给ename创建索引,看看查询其他人的速度。
mysql> alter table EMP add index(ename);
Query OK, 0 rows affected (34.14 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> select * from EMP where ename='king';
+-------+-------+-----------+------+------------+---------+------+--------+
| EMPNO | ENAME | JOB       | MGR  | HIREDATE   | SAL     | COMM | DEPTNO |
+-------+-------+-----------+------+------------+---------+------+--------+
|  7839 | KING  | PRESIDENT | NULL | 1981-11-17 | 5000.00 | NULL |     10 |
+-------+-------+-----------+------+------------+---------+------+--------+
1 row in set (0.00 sec)

mysql>

发现查询速度变快了很多!


2、认识磁盘

2.1、MySQL与存储

MySQL 给用户提供存储服务,而存储的都是数据,数据在磁盘这个外设当中。磁盘是计算机中的一个机械设备,相比于计算机其他电子元件,磁盘效率是比较低的,在加上IO本身的特征,可以知道,如何提交效率,是 MySQL 的一个重要话题。

磁盘结构:我们之前在前面的博客有讲过文件系统inode,链接

我们在前面学习MySQL的时候,比如在创建数据库、表等结构的时候,是会创建对应的文件的。

root@VM-8-12-ubuntu:/var/lib/mysql/test5# ll
total 668880
drwxr-x---  2 mysql mysql      4096 Aug 27 10:46 ./
drwxr-x--- 15 mysql mysql      4096 Aug 18 16:38 ../
-rw-r-----  1 mysql mysql        61 Jul 20 12:06 db.opt
-rw-r-----  1 mysql mysql      8624 Aug 19 13:24 DEPT.frm
-rw-r-----  1 mysql mysql     98304 Aug 19 13:24 DEPT.ibd
-rw-r-----  1 mysql mysql      8586 Jul 25 13:16 duplicate_table.frm
-rw-r-----  1 mysql mysql     98304 Jul 25 13:18 duplicate_table.ibd
-rw-r-----  1 mysql mysql      8780 Aug 27 10:12 EMP.frm
-rw-r-----  1 mysql mysql 683671552 Aug 27 10:13 EMP.ibd
-rw-r-----  1 mysql mysql      8588 Aug 25 21:19 exam.frm
-rw-r-----  1 mysql mysql     98304 Aug 25 21:19 exam.ibd
-rw-r-----  1 mysql mysql      8730 Jul 20 12:53 exam_result.frm
-rw-r-----  1 mysql mysql     98304 Jul 23 20:30 exam_result.ibd
-rw-r-----  1 mysql mysql      8586 Jul 23 20:33 for_delete.frm
-rw-r-----  1 mysql mysql     98304 Jul 23 20:35 for_delete.ibd
-rw-r-----  1 mysql mysql      8586 Jul 25 13:01 for_truncate.frm
-rw-r-----  1 mysql mysql     98304 Jul 25 13:06 for_truncate.ibd
-rw-r-----  1 mysql mysql      8586 Jul 25 13:15 old_duplicate_table.frm
-rw-r-----  1 mysql mysql     98304 Jul 25 13:15 old_duplicate_table.ibd
-rw-r-----  1 mysql mysql      8626 Aug 19 13:30 SALGRADE.frm
-rw-r-----  1 mysql mysql     98304 Aug 19 13:30 SALGRADE.ibd
-rw-r-----  1 mysql mysql      8644 Jul 20 12:07 students.frm
-rw-r-----  1 mysql mysql    114688 Jul 20 12:39 students.ibd
-rw-r-----  1 mysql mysql      8586 Aug 25 21:19 stu.frm
-rw-r-----  1 mysql mysql     98304 Aug 25 21:19 stu.ibd
-rw-r-----  1 mysql mysql      8614 Aug 27 10:46 user.frm
-rw-r-----  1 mysql mysql     98304 Aug 27 13:01 user.ibd
root@VM-8-12-ubuntu:/var/lib/mysql/test5#

所以,最基本的,找到一个文件的全部,本质就是在磁盘找到所有保存文件的扇区。而我们能够定位任何一个扇区,那么便能找到所有扇区,因为查找方式是一样的。

跟你上面磁盘(点链接),我们现在已经能够在硬件层面定位,任何一个基本数据块了(扇区)。那么在系统软件上,就直接按照扇区(512字节,部分4096字节),进行IO交互吗?不是

如果操作系统直接使用硬件提供的数据大小进行交互,那么系统的IO代码,就和硬件强相关。换言之,如果硬件发生变化,系统必须跟着变化。

从目前来看,单次IO 512字节,还是太小了。IO单位小,意味着读取同样的数据内容,需要进行多次磁盘访问,会带来效率的降低。

之前学习文件系统,就是在磁盘的基本结构下建立的,文件系统读取基本单位,就不是扇区,而是数据块

故,系统读取磁盘,是以块为单位的,基本单位是 4KB。


2.2、磁盘随机访问(Random Access)与连续访问(Sequential Access)

  • 随机访问:本次IO所给出的扇区地址和上次IO给出扇区地址不连续,这样的话磁头在两次IO操作之间需要作比较大的移动动作才能重新开始读/写数据。因此较慢。

  • 连续访问:如果当次IO给出的扇区地址与上次IO结束的扇区地址是连续的,那磁头就能很快的开始这次IO操作,这样的多个IO操作称为连续访问。因此较快。

  • 因此尽管相邻的两次IO操作在同一时刻发出,但如果它们的请求的扇区地址相差很大的话也只能称为随机访问,而非连续访问。

  • 磁盘是通过机械运动进行寻址的,随机访问不需要过多的定位,故效率比较高。


3、 MySQL 与磁盘交互基本单位

MySQL 作为一款应用软件,可以想象成一种特殊的文件系统。它有着更高的IO场景,所以,为了提高基本的IO效率, MySQL 进行IO的基本单位是 16KB (后面统一使用 InnoDB 存储引擎讲解)

mysql> show global status like 'innodb_page_size';
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| Innodb_page_size | 16384 |
+------------------+-------+
1 row in set (0.00 sec)

mysql> 

也就是说,磁盘这个硬件设备的基本单位是 512 字节,而 MySQL InnoDB引擎 使用 16KB 进行IO交互。即 MySQL 和磁盘进行数据交互的基本单位是 16KB 。这个基本数据单元,在 MySQL 这里叫做page(注意和系统的page区分)。

也就是创建的时候,直接给该表分配16KB,不够再扩容


4、预备知识

  • MySQL 中的数据文件,是以page为单位保存在磁盘当中的。

  • MySQL 的 CURD 操作,都需要通过计算,找到对应的插入位置,或者找到对应要修改或者查询的数据。

  • 而只要涉及计算,就需要CPU参与,而为了便于CPU参与,一定要能够先将数据移动到内存当中。

  • 所以在特定时间内,数据一定是磁盘中有,内存中也有。后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘。而这时,就涉及到磁盘和内存的数据交互,也就是IO了。而此时IO的基本单位就是Page。

  • 为了更好的进行上面的操作, MySQL 服务器在内存中运行的时候,在服务器内部,就申请了被称为 Buffer Pool 的的大内存空间,来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进行IO交互。

查看buffer pool大小:

mysql> SHOW VARIABLES LIKE 'innodb_buffer_pool_size';
+-------------------------+-----------+
| Variable_name           | Value     |
+-------------------------+-----------+
| innodb_buffer_pool_size | 134217728 |
+-------------------------+-----------+
1 row in set (0.04 sec)

mysql>

图示:

为何更高的效率,一定要尽可能的减少系统和磁盘IO的次数。


5、索引的理解

建立测试表

mysql> create table if not exists user (
->     id int primary key, -- 一定要添加主键哦,只有这样才会默认生成主键索引
->     age int not null,
->     name varchar(16) not null
-> );
Query OK, 0 rows affected (0.07 sec)

mysql>  show create table user \G
*************************** 1. row ***************************
Table: user
Create Table: CREATE TABLE `user` (
`id` int(11) NOT NULL,
`age` int(11) NOT NULL,
`name` varchar(16) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

插入数据

mysql> insert into user (id, age, name) values(3, 18, '杨过');
Query OK, 1 row affected (0.01 sec)

mysql> insert into user (id, age, name) values(4, 16, '小龙女');
Query OK, 1 row affected (0.00 sec)

mysql>  insert into user (id, age, name) values(2, 26, '黄蓉');
Query OK, 1 row affected (0.00 sec)

mysql> insert into user (id, age, name) values(5, 36, '郭靖');
Query OK, 1 row affected (0.00 sec)

mysql>  insert into user (id, age, name) values(1, 56, '欧阳锋');
Query OK, 1 row affected (0.00 sec)

mysql> select *from user; -- 发现是有序的
+----+-----+-----------+
| id | age | name      |
+----+-----+-----------+
|  1 |  56 | 欧阳锋    |
|  2 |  26 | 黄蓉      |
|  3 |  18 | 杨过      |
|  4 |  16 | 小龙女    |
|  5 |  36 | 郭靖      |
+----+-----+-----------+
5 rows in set (0.00 sec)

mysql>

6、为什么IO交互的大小是Page

这里就涉及计算机组成原理的知识了,涉及到局部性原理(简单来说就是现在访问的数据及其相邻的数据,在未来某个时间可能还会访问)

为何MySQL和磁盘进行IO交互的时候,要采用Page的方案进行交互呢?用多少,加载多少不香吗?

如上面的5条记录,如果MySQL要查找id=2的记录,第一次加载id=1,第二次加载id=2,一次一条记录,那么就需要2次IO。

如果要找id=5,那么就需要5次IO。

但,如果这5条(或者更多)都被保存在一个Page中(16KB,能保存很多记录),那么第一次IO查找id=2的时候,整个Page会被加载到MySQL的Buffer Pool中,这里完成了一次IO。但是往后如果在查找id=1,3,4,5等,完全不需要进行IO了,而是直接在内存中进行了。所以,就在单Page里面,大大减少了IO的次数。

你怎么保证,用户一定下次找的数据,就在这个Page里面?我们不能严格保证,但是有很大概率,因为有局部性原理。往往IO效率低下的最主要矛盾不是IO单次数据量的大小,而是IO的次数。


6.1、理解单个Page

6.1.1、单个Page

MySQL 中要管理很多数据表文件,而要管理好这些文件,就需要 先描述,再组织 ,我们目前可以简单理解成一个个独立文件是有一个或者多个Page构成的。

不同的 Page ,在 MySQL 中,都是 16KB ,使用 prev 和 next 构成双向链表

因为有主键的问题, MySQL 会默认按照主键给我们的数据进行排序,从上面的Page内数据记录可以看出,数据是有序且彼此关联的。

为什么数据库在插入数据时要对其进行排序呢?我们按正常顺序插入数据不是也挺好的吗?

答:插入数据时排序的目的,就是优化查询的效率(学完后面B+树就理解了)

页内部存放数据的模块,实质上也是一个链表的结构,链表的特点也就是增删快,查询修改慢,所以优化查询的效率是必须的。

正式因为有序,在查找的时候,从头到后都是有效查找,没有任何一个查找是浪费的,而且,如果运气好,是可以提前结束查找过程。


6.1.2、引入目录

针对上面的单页Page,怎么使得查询效率更高呢?引入目录!

引入目录前,我们查询5号郭靖需要从1开始遍历(或者说效率高使用二分法)。

引入目录后,查询5号,因为5>3,因此我们先找到2[3],然后从3开始往后找,效率更高(这里还可以使用二分法)。

那么这里就可以回答为什么通过主键MySQL要自动排序?方便使用目录结构。


6.2、理解多个Page

6.2.1、多个Page

通过上面的分析,我们知道,上面页模式中,只有一个功能,就是在查询某条数据的时候直接将一整页的数据加载到内存中,以减少硬盘IO次数,从而提高性能。但是,我们也可以看到,现在的页模式内部,实际上是采用了链表的结构,前一条数据指向后一条数据,本质上还是通过数据的逐条比较来取出特定的数据。

如果有1千万条数据,一定需要多个Page来保存1千万条数据,多个Page彼此使用双链表链接起来,而且每个Page内部的数据也是基于链表的。那么,查找特定一条记录,也一定是线性查找。这效率也太低了。

比如上面三个Page的数据都是一张表中的,那我们要找15号郭靖,还是得先从第一个Page开始找!

解决办法:继续引入多级目录(给Page也装上目录)!


6.2.2、引入多级目录

使用一个目录项来指向某一页(Page),而这个目录项存放的就是将要指向的页中存放的最小数据的键值。和页内目录不同的地方在于,这种目录管理的级别是页,而页内目录管理的级别是行。

其中,每个目录项的构成是:键值+指针。图中没有画全。

存在一个目录页来管理页目录,目录页中的数据存放的就是指向的那一页中最小的数据。有数据,就可通过比较,找到该访问那个Page,进而通过指针,找到下一个Page。

其实目录页的本质也是页,普通页中存的数据是用户数据,而目录页中存的数据是普通页的地址。

可是,我们每次检索数据的时候,该从哪里开始呢?虽然顶层的目录页少了,但是还要遍历啊?不用担心,可以在加目录页。

一般来说,三层到四层就到顶了,因为数据也就那么大。比如一个只有索引Page可以存多少索引?16KB/4B=4K条(假设索引占4B)。

假设3层,那么就是4K*4K*16KB=256GB

这货就是传说中的B+树啊(不过B+树的非叶子结点不会链接起来)!没错,至此,我们已经给我们的表user构建完了主键索引。

这里随便查找一个id,只需要访问3次磁盘!


7、InnoDB 在建立索引结构来管理数据的时候,其他数据结构为何不行

  • 链表?线性遍历。
  • 二叉搜索树?退化问题,可能退化成为线性结构。
  • AVL 或者红黑树?虽然是平衡或者近似平衡,但是毕竟是二叉结构,相比较多阶B+,意味着树整体过高,大家都是自顶向下找,层高越低,意味着系统与硬盘更少的IO Page交互。虽然你很秀,但是有更秀的
  • Hash?官方的索引实现方式中, MySQL 是支持HASH的,不过 InnoDB 和 MyISAM 并不支持。Hash跟进其算法特征,决定了虽然有时候也很快(O(1)),不过,在面对范围(一个数据的附近)查找就明显不行,另外还有其他差别,有兴趣可以查一下。

存储引擎底层索引对比:

存储引擎底层索引结构特点
InnoDBB+树InnoDB 是 MySQL 默认的存储引擎,使用 B+树结构的索引来存储数据。B+树结构允许高效的范围查询、顺序访问和快速查找。在 InnoDB 中,主键索引是聚簇索引,数据存储在叶子节点上
MyISAMB+树MyISAM 也使用 B+树结构的索引,但与 InnoDB 不同的是,MyISAM 的索引是非聚簇索引,叶子节点存储的是数据的指针而不是数据本身。因此,MyISAM 的查询速度快于 InnoDB,但不支持事务和外键
TokuDBFractal TreeTokuDB 使用一种叫做 Fractal Tree(分形树)的数据结构,这种结构在处理写操作(特别是批量写操作)时具有更好的性能表现。它适用于大数据集,减少了磁盘 I/O 操作,提供了压缩功能。
RocksDBLSM 树RocksDB 是基于 LSM(Log-Structured Merge)树的存储引擎。LSM 树适合高写入场景,通过将数据分批写入磁盘来减少写入放大。RocksDB 常用于需要高写入性能的场景,如日志系统。
AriaDBB+树Aria 是 MyISAM 的改进版,使用 B+树作为其索引结构。它可以在崩溃恢复时更快地重建索引,且支持并发查询,但写操作性能较 MyISAM 略差。
Memory哈希表Memory 存储引擎将数据存储在内存中,使用哈希表作为索引结构。这种结构使得查找操作非常快,但只适用于需要快速读写的小型数据集,且数据在服务器关闭时会丢失。
CassandraSSTable + LSM树Cassandra 使用 SSTable(Sorted String Table)与 LSM 树的组合来管理数据。LSM 树适合高写入吞吐量的场景,而 SSTable 提供了高效的读操作。Cassandra 广泛用于分布式数据库系统。
  • InnoDB 为何不用B树作为底层索引?

我们下面来对比一下B+树和B树的特性。


7.1、B+树和B树比较

  • B树结构

  • B+树结构

目前这两棵树,对我们最有意义的区别是:

  • B树节点,既有数据,又有Page指针。而B+树,只有叶子节点有数据,其他目录页,只有键值和Page指针
  • B+叶子节点,全部相连(方便范围查找),而B没有
  • 为何选择B+
    • 节点不存储data,这样一个节点就可以存储更多的key。可以使得树更矮,所以IO操作次数更少
    • 叶子节点相连,更便于进行范围查找。

7.2、聚簇索引 VS 非聚簇索引

7.2.1、聚簇索引

InnoDB存储引擎是主键采用聚簇索引,也就是叶子结点直接存储数据。下图为 InnoDB 表的主索引,第一行为主键。

下面我们创建一个使用 InnoDB 存储引擎的表:

  • 终端A:建数据库和建表。
mysql> create database innodb_test;
Query OK, 1 row affected (0.00 sec)

mysql> use innodb_test;
Database changed
mysql> create table itest(
    -> id int primary key,
    -> name varchar(11) not null
    -> )engine=InnoDB; -- 使用InnoDB 存储引擎
Query OK, 0 rows affected (0.03 sec)

mysql>
  • 终端B:查看建表后的文件类型(路径:/var/lib/mysql)。
root@VM-8-12-ubuntu:/var/lib/mysql/innodb_test# ll
total 120
drwxr-x---  2 mysql mysql  4096 Aug 31 10:46 ./
drwxr-x--- 17 mysql mysql  4096 Aug 31 10:44 ../
-rw-r-----  1 mysql mysql    61 Aug 31 10:44 db.opt
-rw-r-----  1 mysql mysql  8586 Aug 31 10:46 itest.frm  -- 表结构数据
-rw-r-----  1 mysql mysql 98304 Aug 31 10:46 itest.ibd  -- 该表对应的主键索引和用户数据,虽然现在一行数据没有,但是该表并不为0,因为有主键索引数据
root@VM-8-12-ubuntu:/var/lib/mysql/innodb_test# 

InnoDB 在叶子结点这种用户数据与索引数据在一起索引方案,叫做聚簇索引


7.2.2、非聚簇索引

MyISAM 引擎同样使用B+树作为索引结果,叶节点的data域存放的是数据记录的地址。下图为 MyISAM 表的主索引,第一列为主键。

其中, MyISAM 最大的特点是,将索引Page和数据Page分离,也就是叶子节点没有数据,只有对应数据的地址。相较于 InnoDB 索引, InnoDB 叶子结点是将索引和数据放在一起的

下面我们创建一个使用 MyISAM 存储引擎的表:

  • 终端A:建数据库和建表。
mysql> create database myisam_test;
Query OK, 1 row affected (0.00 sec)

mysql> use myisam_test;
Database changed
mysql> create table mtest(
    -> id int primary key,
    -> name varchar(11) not null
    -> )engine=MyISAM; -- 使用 MyISAM 存储引擎
Query OK, 0 rows affected (0.01 sec)

mysql>
  • 终端B:查看建表后的文件类型(路径:/var/lib/mysql)。
root@VM-8-12-ubuntu:/var/lib/mysql/myisam_test# ll
total 28
drwxr-x---  2 mysql mysql 4096 Aug 31 10:37 ./
drwxr-x--- 16 mysql mysql 4096 Aug 31 10:34 ../
-rw-r-----  1 mysql mysql   61 Aug 31 10:34 db.opt
-rw-r-----  1 mysql mysql 8586 Aug 31 10:37 mtest.frm  -- 表结构数据
-rw-r-----  1 mysql mysql    0 Aug 31 10:37 mtest.MYD  -- 该表对应的数据,当前没有数据,所以是0
-rw-r-----  1 mysql mysql 1024 Aug 31 10:37 mtest.MYI  -- 该表对应的主键索引数据
root@VM-8-12-ubuntu:/var/lib/mysql/myisam_test#

MyISAM 在叶子结点这种用户数据与索引数据分离的索引方案,叫做非聚簇索引


7.2.3、非主键索引

上边两种索引,只介绍了主键索引。但是它们都可以有非主键索引(普通索引)(需要自己手动设置,下面会讲)。

  • 对于 MyISAM 的普通索引,和主键索引差不多,只是主键不能重复,而非主键可以重复。如下图:

  • InnoDB 除了主键索引,用户也会建立辅助(普通)索引。

可以看到, InnoDB 的非主键索引中叶子节点并没有数据,而只有对应记录的key值。所以通过辅助(普通)索引,找到目标记录,需要两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。这种过程,就叫做回表查询。为何InnoDB 针对这种辅助(普通)索引的场景,不给叶子节点也附上数据呢?原因就是太浪费空间了。


8、索引操作

8.1、创建主键索引

  • 第一种方式:在建表的时候,指定主键(设置primary key)。
create table user1(id int primary key,name varchar(11) not null);

或者

create table user2(id int,name varchar(11) not null,primary key(id));
  • 第二种方式:建表之后再添加主键。
create table user3(id int,name varchar(11) not null);
alter table user3 add primary key(id);

主键索引的特点:

  • 一个表中,最多有一个主键索引,当然可以是复合主键
  • 主键索引的效率高(主键不可重复)
  • 创建主键索引的列,它的值不能为null,且不能重复
  • 主键索引的列基本上是int

8.2、创建唯一索引

和主键差不多。

  • 第一种方式:在建表的时候,指定唯一键(设置unique [key])。
create table user4(id int primary key,name varchar(11) unique);

或者

create table user5(id int,name varchar(11),primary key(id),unique(name));
  • 第二种方式:建表之后再添加唯一键。
create table user6(id int,name varchar(11));
alter table user6 add unique key(name);

唯一索引的特点:

  • 一个表中,可以有多个唯一索引
  • 查询效率高
  • 如果在某一列建立唯一索引,必须保证这列不能有重复数据
  • 如果一个唯一索引上指定not null,接近于主键索引

8.3、创建普通索引索引

  • 第一种方式:在建表的时候,在表的最后,指定某列为索引。
create table user7(id int,name varchar(11),index(name));
  • 第二种方式:建表之后再添加索引。
create table user8(id int,name varchar(11));
alter table user8 add index(name);
  • 第三种方式:创建一个索引,可以指定名字,不指定名字,则索引名默认为字段名。
create table user9(id int,name varchar(11));
create index name [索引名] on user9;

普通索引的特点:

  • 一个表中可以有多个普通索引,普通索引在实际开发中用的比较多
  • 如果某列需要创建索引,但是该列有重复的值,那么我们就应该使用普通索引

8.4、创建全文索引

当对文章字段或有大量文字的字段进行检索时,会使用到全文索引。MySQL提供全文索引机制,但是有要求,要求表的存储引擎必须是MyISAM,而且默认的全文索引支持英文,不支持中文。如果对中文进行全文检索,可以使用sphinx的中文版(coreseek)。

建表并插入数据:

mysql> CREATE TABLE articles (
   -> id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
   -> title VARCHAR(200),
   -> body TEXT,
   -> FULLTEXT (title,body)
   -> )engine=MyISAM;
Query OK, 0 rows affected (0.01 sec)

mysql> INSERT INTO articles (title,body) VALUES
   -> ('MySQL Tutorial','DBMS stands for DataBase ...'),
   -> ('How To Use MySQL Well','After you went through a ...'),
   -> ('Optimizing MySQL','In this tutorial we will show ...'),
   -> ('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
   -> ('MySQL vs. YourSQL','In the following database comparison ...'),
   -> ('MySQL Security','When configured properly, MySQL ...');
Query OK, 6 rows affected (0.00 sec)
Records: 6  Duplicates: 0  Warnings: 0

mysql>
  • 查询全文是否有含有内容database。

  • 不使用全文索引:

    mysql> select * from articles where body like '%database%';
    +----+-------------------+------------------------------------------+
    | id | title             | body                                     |
    +----+-------------------+------------------------------------------+
    |  1 | MySQL Tutorial    | DBMS stands for DataBase ...             |
    |  5 | MySQL vs. YourSQL | In the following database comparison ... |
    +----+-------------------+------------------------------------------+
    2 rows in set (0.00 sec)
    
    mysql> 
    

可以用explain工具看一下,是否使用到索引:

mysql> explain select * from articles where body like '%database%'\G
*************************** 1. row ***************************
          id: 1
 select_type: SIMPLE
       table: articles
  partitions: NULL
        type: ALL
possible_keys: NULL
         key: NULL  -- NULL表示没有使用到索引
     key_len: NULL
         ref: NULL
        rows: 6
    filtered: 16.67
       Extra: Using where
1 row in set, 1 warning (0.00 sec)

mysql>

那怎么使用全文索引呢?

使用下面语句:

mysql> select * from articles where match(title,body) against('database');
+----+-------------------+------------------------------------------+
| id | title             | body                                     |
+----+-------------------+------------------------------------------+
|  5 | MySQL vs. YourSQL | In the following database comparison ... |
|  1 | MySQL Tutorial    | DBMS stands for DataBase ...             |
+----+-------------------+------------------------------------------+
2 rows in set (0.00 sec)

使用explain工具查看是否使用索引:

mysql> explain select * from articles where match(title,body) against('database')\G
*************************** 1. row ***************************
          id: 1
 select_type: SIMPLE
       table: articles
  partitions: NULL
        type: fulltext  -- 索引类型是全文索引
possible_keys: title
         key: title  	 -- 使用了索引
     key_len: 0
         ref: const
        rows: 1
    filtered: 100.00
       Extra: Using where
1 row in set, 1 warning (0.00 sec)

mysql>

8.5、查询索引

使用user1表:

create table user1(id int primary key,name varchar(11) not null);
  • 第一种方法:
show keys from 表名;

例如:

mysql> show keys from user1\G
*************************** 1. row ***************************
        Table: user1
   Non_unique: 0
     Key_name: PRIMARY  -- 索引名
 Seq_in_index: 1
  Column_name: id       
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: 
   Index_type: BTREE    -- 这里的BTREE就是B+树
      Comment: 
Index_comment: 
1 row in set (0.00 sec)
  • 第二种方法:
show index from 表名;

例如:

mysql> show index from user1\G
*************************** 1. row ***************************
        Table: user1
   Non_unique: 0
     Key_name: PRIMARY
 Seq_in_index: 1
  Column_name: id
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: 
   Index_type: BTREE
      Comment: 
Index_comment: 
1 row in set (0.00 sec)
  • 第三种方法:这个方法显示的信息简略,只能看到主键(索引)。
desc 表名;

8.6、删除索引

  • 删除主键索引
alter table 表名 drop primary key;
  • 其他索引的删除:

  • 第一种方法:

    alter table 表名 drop index 索引名;
    
  • 第二种方法:

    mysql> show keys from user1\G
    *************************** 1. row ***************************
            Table: user1
       Non_unique: 0
         Key_name: PRIMARY  -- 索引名
     Seq_in_index: 1
      Column_name: id
        Collation: A
      Cardinality: 0
         Sub_part: NULL
           Packed: NULL
             Null: 
       Index_type: BTREE
          Comment: 
    Index_comment: 
    *************************** 2. row ***************************
            Table: user1
       Non_unique: 1
         Key_name: name  -- 索引名
     Seq_in_index: 1
      Column_name: name
        Collation: A
      Cardinality: 0
         Sub_part: NULL
           Packed: NULL
             Null: 
       Index_type: BTREE
          Comment: 
    Index_comment: 
    2 rows in set (0.00 sec)
    
    mysql> alter table user1 drop index name;
    Query OK, 0 rows affected (0.02 sec)
    Records: 0  Duplicates: 0  Warnings: 0
    
    mysql> show keys from user1\G
    *************************** 1. row ***************************
            Table: user1
       Non_unique: 0
         Key_name: PRIMARY
     Seq_in_index: 1
      Column_name: id
        Collation: A
      Cardinality: 0
         Sub_part: NULL
           Packed: NULL
             Null: 
       Index_type: BTREE
          Comment: 
    Index_comment: 
    1 row in set (0.00 sec)
    
    mysql>
    

    索引名就是show keys from 表名中的Key_name 字段

mysql> alter table user10 drop index idx_name;

  • 第三种方法:drop index 索引名 on 表名
mysql> show keys from user1\G
*************************** 1. row ***************************
        Table: user1
   Non_unique: 0
     Key_name: PRIMARY
 Seq_in_index: 1
  Column_name: id
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: 
   Index_type: BTREE
      Comment: 
Index_comment: 
*************************** 2. row ***************************
        Table: user1
   Non_unique: 1
     Key_name: name
 Seq_in_index: 1
  Column_name: name
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: 
   Index_type: BTREE
      Comment: 
Index_comment: 
2 rows in set (0.00 sec)

mysql> drop index name from user1;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'from user1' at line 1
mysql> drop index name on user1;
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> show keys from user1\G
*************************** 1. row ***************************
        Table: user1
   Non_unique: 0
     Key_name: PRIMARY
 Seq_in_index: 1
  Column_name: id
    Collation: A
  Cardinality: 0
     Sub_part: NULL
       Packed: NULL
         Null: 
   Index_type: BTREE
      Comment: 
Index_comment: 
1 row in set (0.00 sec)

mysql>

8.7、索引创建规则

  • 比较频繁作为查询条件的字段应该创建索引
  • 唯一性(有很多相同值,不好找)太差的字段不适合单独创建索引,即使频繁作为查询条件
  • 更新非常频繁的字段不适合作创建索引(需要对B+树进行调整)
  • 不会出现在where子句中的字段不该创建索引(不怎么用)

OKOK,MySQL索引就到这里,如果你对Linux和C++也感兴趣的话,可以看看我的主页哦。下面是我的github主页,里面记录了我的学习代码和leetcode的一些题的题解,有兴趣的可以看看。

Xpccccc的github主页

MySQL索引是一种数据结构,可以帮助MySQL快速定位和访问表中的数据。使用索引可以提高查询效率,降低数据库的负载。下面是MySQL索引的一些基本概念和使用方法: 1. 索引类型 MySQL支持多种类型的索引,包括B树索引、哈希索引、全文索引等。其中,B树索引是最常用的一种,也是默认的索引类型。B树索引可以用于精确匹配和范围查询,而哈希索引主要用于等值查询,全文索引则用于文本检索。 2. 索引创建 可以在创建表时指定索引,例如: ``` CREATE TABLE users ( id INT PRIMARY KEY, name VARCHAR(50), email VARCHAR(50), INDEX idx_email (email) ); ``` 也可以在已有的表上添加索引,例如: ``` ALTER TABLE users ADD INDEX idx_name (name); ``` 3. 索引使用 查询语句中可以使用WHERE子句和ORDER BY子句来利用索引,例如: ``` SELECT * FROM users WHERE email = 'example@example.com'; SELECT * FROM users WHERE name LIKE 'John%' ORDER BY id DESC; ``` 需要注意的是,索引并不是越多越好,过多的索引会占用过多的磁盘空间并降低写操作的性能。因此,需要根据实际情况选择合适的索引。同时,还需要定期对索引进行维护,包括优化查询语句、删除不必要的索引等。 4. 索引优化 MySQL提供了一些工具来优化索引,例如EXPLAIN命令可以帮助分析查询语句的执行计划,找出慢查询和不必要的全表扫描。可以使用OPTIMIZE TABLE命令来优化表的索引和碎片,从而提高查询性能。还可以使用缓存来避免频繁的查询操作,例如使用Memcached或Redis等缓存工具。 以上就是MySQL索引的一些基本概念和使用方法,需要根据实际情况进行选择和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Xpccccc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值