数据库中专门用于帮助用户快速查找数据的一种数据结构。类似于字典中的目录,查找字典内容时可以根据目录查找到数据的存放位置吗,然后直接获取。
索引的作用是约束和加速查找
无索引: 从前往后一条一条查询
有索引:创建索引的本质,就是创建额外的文件(以某种格式存储),查询的时候,先去额外的文件找,定好位置,然后再去原始表中直接查询。但是创建索引越多,对硬盘也是有损耗的。
注意:建立索引后查询加快,但是插入更新删除依然慢;创建索引之后,必须命中索引才会达到加速查询的效果。
单列:普通索引,唯一索引,主键索引
多列:联合索引(组合索引),包括联合主键索引、联合唯一索引、联合普通索引
加速查找 + unique(约束) 可以为空
只能加速查找
语法为create index ix_name on userinfo(name);
加速查找 + 约束(不为空)
注意:主键索引比普通索引快
索引按类型分为hash索引和BTree索引
hash索引生成的hash值是无序的,所以查询单条快,范围查询(> < like)慢
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)
);
给已有表添加索引
create index 索引的名字 on 表名(列名)
删除索引
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)
);
组合索引是将多个列组合成一个索引
create index 索引名 on 表名(列名1,列名2);
覆盖索引:在索引文件中直接获取数据(不需要回到数据表)
例如把name设为索引
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_insert1\G
#4. 调用存储过程
call auto_insert1();
上面这种方式太慢了,300万条数据,最快也要几个小时
下面我们使用协程来帮助我们,插入300万条数据,只需要几十秒
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)
运行结果
成功插入第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‘的记录
mysql> select * from userinfo where name='alice1929876';
+---------+--------------+--------+--------------------+
| id | name | gender | email |
+---------+--------------+--------+--------------------+
| 1929876 | alice1929876 | female | girl1929876@qq.com |
+---------+--------------+--------+--------------------+
1 row in set (1.38 sec)
可以看到查询速度是比较慢的
现在我们给userinfo表的name字段建立索引
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‘ 时,因为%在前面,相当于包括了所有,所以这种情况下会进行全表扫描,而不会使用索引。
不会使用索引的情况
最左前缀是组合索引里的说法,意为组合索引会先匹配前面的字段
删除ix_name索引,避免干扰联合索引
drop index ix_name on userinfo;
创建联合索引
mysql> create index ix_name_email on userinfo(name,email);
Query OK, 0 rows affected (9.71 sec)
Records: 0 Duplicates: 0 Warnings: 0
设置name和email组合索引后,查询name,email字段会命中吗?来看看
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字段命中了
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一起是否会命中
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 | 是 |
否 |
原文:https://www.cnblogs.com/zzliu/p/10664896.html