mysql索引

一、索引简介

数据库中专门用于帮助用户快速查找数据的一种数据结构。类似于字典中的目录,查找字典内容时可以根据目录查找到数据的存放位置吗,然后直接获取。


## 二、索引的作用 索引的作用是约束和加速查找
## 三、无索引与有索引的区别 无索引: 从前往后一条一条查询

有索引:创建索引的本质,就是创建额外的文件(以某种格式存储),查询的时候,先去额外的文件找,定好位置,然后再去原始表中直接查询。但是创建索引越多,对硬盘也是有损耗的。

注意:建立索引后查询加快,但是插入更新删除依然慢;创建索引之后,必须命中索引才会达到加速查询的效果


## 四、常见的几种索引

单列:普通索引,唯一索引,主键索引

多列:联合索引(组合索引),包括联合主键索引、联合唯一索引、联合普通索引

唯一索引

加速查找 + unique(约束) 可以为空

普通索引

只能加速查找
语法为create index ix_name on userinfo(name);

主键索引

加速查找 + 约束(不为空)

注意:主键索引比普通索引快


## 五、索引的类型 索引按类型分为hash索引和BTree索引

hash索引

hash索引生成的hash值是无序的,所以查询单条快,范围查询(> < like)慢

BTree索引

B+树,层数越多,数据量呈指数增长,BTree索引是innodb的默认索引


## 六、索引的使用

普通索引

普通索引仅有一个作用:加速查找

创建表的时候添加索引

create table userinfo(
   nid int not null auto_increment primary key,
   name varchar(32) not null,
   email varchar(64) not null,
   index ix_name(name)
);


给已有表添加索引 ```sql create index 索引的名字 on 表名(列名)

删除索引
```sql
drop index 索引的名字 on 表名

查看索引

show index from 表名


### 唯一索引 唯一索引的两个作用:加速查找和唯一约束(可以为null)

创建表的时候添加唯一索引

create table userinfo(
   id int not null auto_increment primary key,
   name varchar(32) not null,
   email varchar(64) not null,
   unique  index  ix_name(name)
);

给已有的表添加唯一索引

create unique index 索引名 on 表名(列名)

删除索引

drop unique index 索引名 on 表名

### 主键索引

主键索引的两个作用:加速查找和唯一约束(不能为null)

创建表的时候添加主键索引

create table userinfo(
    id int not null auto_increment primary key,  # 主键索引
    name varchar(32) not null,
    email varchar(64) not null,
    unique  index  ix_name(name)
);
或者
create table userinfo(
    id int not null auto_increment,
    name varchar(32) not null,
    email varchar(64) not null,
    primary key(id),
    unique  index  ix_name(name)
);


### 组合索引 组合索引是将多个列组合成一个索引 ```sql create index 索引名 on 表名(列名1,列名2);
<br>
## 覆盖索引和索引合并

覆盖索引:在索引文件中直接获取数据(不需要回到数据表)
例如把name设为索引
```sql
select name from userinfo where name='mandy';

索引合并:把多个单列索引合并使用

select * from userinfo where name='sophia' and id=12;


七、利用索引加快查询速度

数据库中添加索引确实会让查询速度加快,但是前提是要命中索引

准备数据

准备300w条数据

#1. 准备表
create table userinfo(
id int,
name varchar(20),
gender char(6),
email varchar(50)
);

#2. 创建存储过程,实现批量插入300万条记录
delimiter $$ #声明存储过程的结束符号为$$
create procedure auto_insert1()
BEGIN
    #设置变量1,默认值为1
    declare i int default 1;
    while(i<=3000000)do
       #concat,字符串拼接。当i为1时,那么concat('alex',i)表示为alex1
        insert into userinfo values(i,concat('alex',i),'male',concat('egon',i,'@oldboy'));
        set i=i+1;
    end while;
END$$ #$$结束
delimiter ; #重新声明分号为结束符号

#3. 查看存储过程
show create procedure auto_insert1G 

#4. 调用存储过程
call auto_insert1();


上面这种方式太慢了,300万条数据,最快也要几个小时 下面我们使用协程来帮助我们,插入300万条数据,只需要几十秒 ```python import pymysql import gevent import time

class MyPyMysql:
def init(self, host, port, username, password, db, charset='utf8'):
self.host = host # mysql主机地址
self.port = port # mysql端口
self.username = username # mysql远程连接用户名
self.password = password # mysql远程连接密码
self.db = db # mysql使用的数据库名
self.charset = charset # mysql使用的字符编码,默认为utf8
self.pymysql_connect() # __init__初始化之后,执行的函数

def pymysql_connect(self):
    # 用pymysql连接数据库
    self.conn = pymysql.connect(
        host=self.host,
        port=self.port,
        user=self.username,
        password=self.password,
        db=self.db,
        charset=self.charset,
    )
    # 连接mysql之后执行的函数
    self.asynchronous()

def run(self, nmin, nmax):
    # 创建游标
    self.cur = self.conn.cursor()

    # 定义sql语句,插入id,name,gender,email
    sql = 'insert into userinfo(id,name,gender,email) values (%s,%s,%s,%s)'

    # 定义总插入行数为一个空列表
    data_list = []
    for i in range(nmin, nmax):
        # 添加所有任务到总的任务列表
        result = (i, 'alice' + str(i), 'female', 'girl' + str(i) + '@qq.com')
        data_list.append(result)

    # 执行多行插入,executemany,需要接收的第一个参数为sql语句,第二个参数为序列
    content = self.cur.executemany(sql, data_list)
    if content:
        print('成功插入第{}条数据'.format(nmax-1))

    # 提交数据,必须提交,不然数据不会保存
    self.conn.commit()

def asynchronous(self):
    # g_l任务列表
    # 定义了异步的函数:这里用到了gevent.spawn方法
    max_line = 10000  # 定义每次插入行数,即一次插入10000行
    # spawn开启协程,第一个参数为执行的函数,后面为协程函数传入的参数,返回一个greenlet对象
    g_l = [gevent.spawn(self.run, i, i+max_line) for i in range(1, 3000001, max_line)]

    # gevent.joinall()  等待所有操作都执行完毕
    gevent.joinall(g_l)
    self.cur.close()    # 关闭游标
    self.conn.close()   # 关闭pymysql连接

if name == 'main':
start_time = time.time()
st = MyPyMysql('127.0.0.1', 3306, 'root', '123', 'db2') # 实例化类,传入参数
end_time = time.time()
delta_time = end_time-start_time
print('程序耗时:%s秒' % delta_time)


<br>
运行结果
```python
成功插入第2970000条数据
成功插入第2980000条数据
成功插入第2990000条数据
成功插入第3000000条数据
程序耗时:38.6165976524353秒

可以看到,之间大大缩短了,只用了38秒!

这是因为,一般插入表数据是这样的


insert into userinfo(id,name,gender,email) values ('1','alex1','male','egon1@oldboy')


使用协程插入数据是这样的

insert into userinfo(id,name,gender,email) values ('1','alex1','male','egon1@oldboy'),('2','alex2','male','egon2@oldboy'),('3','alex3','male','egon3@oldboy')...后面有1万个元组

一次插入10000条的效率显然比一次1条高,同时,方式二开启了300个协程,遇到I/O切换,综合起来效率就大大高于方式一了


### 测试

注意:现在还没有建立任何索引!

查询id=2834567的记录

mysql> select * from userinfo where id=2834567;
+---------+--------------+--------+--------------------+
| id      | name         | gender | email              |
+---------+--------------+--------+--------------------+
| 2834567 | alice2834567 | female | girl2834567@qq.com |
+---------+--------------+--------+--------------------+
1 row in set (1.13 sec)

查询name='alice1929876'的记录 ```sql mysql> select * from userinfo where name='alice1929876'; +---------+--------------+--------+--------------------+ | id | name | gender | email | +---------+--------------+--------+--------------------+ | 1929876 | alice1929876 | female | girl1929876@qq.com | +---------+--------------+--------+--------------------+ 1 row in set (1.38 sec)
可以看到查询速度是比较慢的

<br>
现在我们给userinfo表的name字段建立索引

```sql
mysql> create index ix_name on userinfo(name);
Query OK, 0 rows affected (8.10 sec)
Records: 0  Duplicates: 0  Warnings: 0


可以看到给已经有很多数据的表建立索引是非常耗时的,因为要扫描数据生成索引文件

使用索引查询name='alice2340268'的记录

mysql> select * from userinfo where name='alice2340268';
+---------+--------------+--------+--------------------+
| id      | name         | gender | email              |
+---------+--------------+--------+--------------------+
| 2340268 | alice2340268 | female | girl2340268@qq.com |
+---------+--------------+--------+--------------------+
1 row in set (0.01 sec)

与没有索引相比,查询速度快了一百多倍!


前面提到,需要命中索引才能提高查询速度,下面演示一种没有命中索引的情况
mysql> select * from userinfo where name like '%ice4586';
+------+-----------+--------+-----------------+
| id   | name      | gender | email           |
+------+-----------+--------+-----------------+
| 4586 | alice4586 | female | girl4586@qq.com |
+------+-----------+--------+-----------------+
1 row in set (1.63 sec)

当使用 like '%xx' 时,因为%在前面,相当于包括了所有,所以这种情况下会进行全表扫描,而不会使用索引


### 小结

不会使用索引的情况

  • 比较的字段未设置索引时
  • like模糊匹配通配符在前时 like '%xx'
  • 条件是 > < != 时(主键或索引是整数类型时还是会走索引)

### 最左前缀 最左前缀是组合索引里的说法,意为组合索引会先匹配前面的字段

删除ix_name索引,避免干扰联合索引

drop index ix_name on userinfo;


创建联合索引 ```sql mysql> create index ix_name_email on userinfo(name,email); Query OK, 0 rows affected (9.71 sec) Records: 0 Duplicates: 0 Warnings: 0
<br>

设置name和email组合索引后,查询name,email字段会命中吗?来看看

#### 查询name

```sql
mysql> select * from userinfo where name='alice234239';
+--------+-------------+--------+-------------------+
| id     | name        | gender | email             |
+--------+-------------+--------+-------------------+
| 234239 | alice234239 | female | girl234239@qq.com |
+--------+-------------+--------+-------------------+
1 row in set (0.01 sec)

显然name字段命中了


#### 查询email ```sql mysql> select * from userinfo where email='girl1830002@qq.com'; +---------+--------------+--------+--------------------+ | id | name | gender | email | +---------+--------------+--------+--------------------+ | 1830002 | alice1830002 | female | girl1830002@qq.com | +---------+--------------+--------+--------------------+ 1 row in set (1.34 sec)
email字段没有命中!!!这就是最左前缀,为了验证,我们再来看下name和email一起是否会命中

<br>
#### 查询name和email
```sql
mysql> select * from userinfo where name='alice989986' and email='girl989986@qq.com';
+--------+-------------+--------+-------------------+
| id     | name        | gender | email             |
+--------+-------------+--------+-------------------+
| 989986 | alice989986 | female | girl989986@qq.com |
+--------+-------------+--------+-------------------+
1 row in set (0.01 sec)

name和email命中了


小结

当使用name和email作为组合索引时

字段 是否使用索引
name
name和email
email

## 八、索引的注意事项 - 避免使用select * - 用count(1)或count(列)代替count(*) - 创建表时尽量使用char代替varchar - 表的字段顺序固定长度的字段优先 - 组合索引代替多个单列索引(经常使用多个条件查询时) - 尽量使用短索引 - 使用连接(join)来代替子查询 - 连表时注意条件类型一致 - 索引散列(重复少)不适合建索引

关于第7点,mysql5.7版本没有区别

原文地址:https://www.cnblogs.com/zzliu/p/10664896.html