select * from t_emp a INNER JOIN t_dept b on a.deptId = b.id
select * from t_emp a left JOIN t_dept b on a.deptId = b.id
select * from t_dept b
select * from t_emp a left JOIN t_dept b on a.deptId = b.id where b.id is null
select * from t_dept b left JOIN t_emp a on a.deptId = b.id where a.id is null
select a.*,b.* from t_emp a left JOIN t_dept b on a.deptId = b.id UNION select a.*,b.* from t_dept b left JOIN t_emp a on a.deptId = b.id where a.id is null
select a.*,b.* from t_emp a left JOIN t_dept b on a.deptId = b.id where b.id is null UNION select a.*,b.* from t_dept b left JOIN t_emp a on a.deptId = b.id where a.id is null
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。可以得到索引的本质:索引是数据结构。
在数据之外,数据库系统还维护着满足特定查找算法的数据结构,这些数据结构以某种方式引用(指向)数据,这样就可以在这些数据结构上实现高级查找算法。这种数据结构,就是索引。下图就是一种可能的索引方式示例
左边是数据表,一共有两列七条记录,最左边的是数据记录的物理地址为了加快Col2的查找,可以维护一个右边所示的二叉查找树,每个节点分别包含索引键值和一个指向对应数据记录物理地址的指针,这样就可以运用二叉查找在一定的复杂度内获取到相应数据,从而快速的检索出符合条件的记录。
注意:一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上
优势:
劣势:
一颗b树,浅蓝色的块我们称之为一个磁盘块,可以看到每个磁盘块包含几个数据项(深蓝色所示)和指针(黄色所示),如磁盘块1包含数据项17和35,包含指针P1、P2、P3,
P1表示小于17的磁盘块,P2表示在17和35之间的磁盘块,P3表示大于35的磁盘块。
真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。
非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
查找过程:
如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,此时发生一次IO,在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,内存时间因为非常短(相比磁盘的IO)可以忽略不计,通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,发生第二次IO,29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,发生第三次IO,同时内存中做二分查找找到29,结束查询,总计三次IO。
真实的情况是,3层的b+树可以表示上百万的数据,如果上百万的数据查找只需要三次IO,性能提高将是巨大的,如果没有索引,每个数据项都要发生一次IO,那么总共需要百万次的IO,显然成本非常非常高。
为什么说B+树比B-树更适合实际应用中操作系统的文件索引和数据库索引?
索引创建的基本语法
创建:CREATE [UNIQUE ] INDEX [indexName] ON table_name(column)) 加上unique就是创建唯一索引
删除:DROP INDEX [indexName] ON mytable;
查看:SHOW INDEX FROM table_name
修改:有四种方式来添加数据表的索引:
4.1 ALTER TABLE tbl_name ADD PRIMARY KEY (column_list): 该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
4.2 ALTER TABLE tbl_name ADD UNIQUE index_name (column_list): 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
4.3 ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出现多次。
4.4 ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT ,用于全文索引。
即一个索引只包含单个列,一个表可以有多个单列索引
# 随表一起建索引: CREATE TABLE customer ( id INT(10) UNSIGNED AUTO_INCREMENT , customer_no VARCHAR(200), customer_name VARCHAR(200), PRIMARY KEY(id), KEY (customer_name) ); # 单独建单值索引: CREATE INDEX idx_customer_name ON customer(customer_name); # 删除索引: DROP INDEX idx_customer_name on customer;
索引列的值必须唯一,但允许有空值
# 随表一起建索引: CREATE TABLE customer ( id INT(10) UNSIGNED AUTO_INCREMENT , customer_no VARCHAR(200), customer_name VARCHAR(200), PRIMARY KEY(id), KEY (customer_name), UNIQUE (customer_no) ); # 单独建唯一索引: CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no); # 删除索引: DROP INDEX idx_customer_no on customer ;
设定为主键后数据库会自动建立索引,innodb为聚簇索引
# 随表一起建索引: CREATE TABLE customer ( id INT(10) UNSIGNED AUTO_INCREMENT , customer_no VARCHAR(200), customer_name VARCHAR(200), PRIMARY KEY(id) ); CREATE TABLE customer2 ( id INT(10) UNSIGNED , customer_no VARCHAR(200), customer_name VARCHAR(200), PRIMARY KEY(id) ); # 单独建主键索引: ALTER TABLE customer add PRIMARY KEY customer(customer_no); # 删除建主键索引: ALTER TABLE customer drop PRIMARY KEY ; #修改建主键索引: #必须先删除掉(drop)原索引,再新建(add)索引
即一个索引包含多个列
# 随表一起建索引: CREATE TABLE customer ( id INT(10) UNSIGNED AUTO_INCREMENT , customer_no VARCHAR(200), customer_name VARCHAR(200), PRIMARY KEY(id), KEY (customer_name), UNIQUE (customer_name), KEY (customer_no,customer_name) ); 单独建索引: CREATE INDEX idx_no_name ON customer(customer_no,customer_name); 删除索引: DROP INDEX idx_no_name on customer ;
使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理你的SQL语句的。分析你的查询语句或是表结构的性能瓶颈。
Explain + SQL语句
实例
explain select a.NAME,c.NAME from t_emp a left join t_dept b on a.deptId = b.id left join t_emp c on b.ceo=c.id
上图中字段的解释
id:表示查询中执行select子句或操作表的顺序
1.1 id相同,执行顺序由上至下
1.2 id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行(例如子查询)
1.3 id相同不同,同时存在
select_type:查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询
table:显示这一行的数据是关于哪张表的
partitions:代表分区表中的命中情况,非分区表,该项为null
type:访问类型排列 显示查询使用了何种类型,从最好到最差依次是:system>const>eq_ref>ref>range>index>ALL
先看两张表
dept表
emp表
系统中经常出现以下语句
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4 EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 and deptid=4 AND emp.name = 'abcd'
在建立索引前进行查询语句执行
select SQL_NO_CACHE * from emp where emp.age=30 and deptId = 4 and emp.name = 'abcd'
结果
进行添加索引
CREATE INDEX idx_age_deptid_name ON emp(age,deptid,NAME)
再次执行查询语句
select SQL_NO_CACHE * from emp where emp.age=30 and deptId = 4 and emp.name = 'abcd'
结果
如果系统中出现以下sql,那么原来的索引还能使用吗?
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.age=30 AND emp.name = 'abcd'
可以看到虽然用上了上述创建的索引,但是只有部分被用上了
结论:过滤条件要使用索引必须按照索引建立时的顺序,依次满足,一旦跳过某个字段,索引后面的字段都无法被使用。
不在索引列上做任何操作(计算、函数、(自动or手动)类型转换),会导致索引失效而转向全表扫描
执行以下sql语句,在不包含索引的情况下
EXPLAIN SELECT SQL_NO_CACHE avg(emp.age) FROM emp WHERE emp.age=30 AND emp.name = 'abcd'
结果
给age创建索引
create index idx_age on emp(age)
再次执行查询语句 结果如下图
可以看到没有任何的优化
存储引擎不能使用索引中范围条件右边的列
给name创建索引
CREATE INDEX idx_name ON emp(NAME)
sql语句name=XXX
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name = 'abc'
结果:可以看到key中显示了刚刚创建的索引
sql语句name!=XXX或者sql语句name<>XXX
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name <> 'abc'
结果:可以看到key没有任何索引
EXPLAIN SELECT * FROM emp WHERE age IS NULL
EXPLAIN SELECT * FROM emp WHERE age IS NOT NULL
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name like '_abc%'
注意:如果like后面的字符串不加%或者_,也是会使用索引的
EXPLAIN SELECT SQL_NO_CACHE * FROM emp WHERE emp.name = 123
假设index(a,b,c)
尽量不要使用not in 或者 not exists 用left outer join on xxx is null 替代