MyISAM | InnoDB | |
---|---|---|
事务支持 | 不支持事务 | 支持事务 |
事务支持 | 使用表级锁定,当一个查询需要修改表中的数据时,会锁定整个表,这可能导致并发性能下降。 | 使用行级锁定,只锁定需要修改的行,允许多个事务同时处理同一表的不同部分,提高并发性能。 |
外键 | 不支持 | 支持 |
性能 | 在读密集型工作负载下表现较好,因为它的表级锁定和简单的结构。 | 在写密集型工作负载和需要事务支持的应用中表现较好,因为它的行级锁定和更复杂的结构。 |
全文索引 | 支持 | 不支持 |
如何选择存储引擎?
索引相当于目录, 为了方便查询书中的内容, 通过对内容建立索引形成目录., 其中包含对数据表中所有记录的引用指针, 索引是一个文件, 它会占用物理空间
值唯一性
,提高数据检索效率主键列
或唯一键列
的值。外键通常用来进行表关联操作
, 设置索引可以提高连接操作的性能# 学生信息表
create table stuInfo(
Scode int primary key,
Sname char(10),
Saddress varchar(50),
Sgrade int,
Semail varchar(50),
Sbrith date
)DEFAULT CHARSET='utf8';
# 分数表
create table score(
studentID int,
coureseID int,
score int,
scoreID int primary key,
foreign key(studentID) references stuInfo(Scode)
)DEFAULT charset='utf8';
WHERE
子句中使用的字段ORDER BY
子句中使用的字段
GROUP BY
子句中使用的字段
ON
涉及的字段, 建立索引能提高检索效率BETWEEN
、>
, <
, <=
, >=
等比较运算符的查询。FULLTEXT索引
提高检索效率, 当然, 更推荐使用全文检索引擎来实现全文检索的功能, 例如Elasticsearch等当然即使满足了上述条件, 还是需要结合索引原则
来判断是否需要建立索引
ALTER TABLE table_name ADD UNIQUE (column);
创建唯一索引ALTER TABLE table_name ADD UNIQUE (column1,column2);
创建唯一组合索引ALTER TABLE table_name ADD INDEX index_name (column);
创建普通索引ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);
创建组合索引ALTER TABLE table_name ADD FULLTEXT (column);
创建全文索引InnoDB的索引类型目前只有两种, 经常使用的InnoDB存储引擎的默认索引实现为:B+树索引
参考: 数据结构之“树”——二叉树、红黑树、B树、B+树、B*树
二叉树的缺点: 难以保证树的平衡, 顺序插入时, 会变成一个链表, 查询性能大大降低
红黑树是一种自平衡的二叉树, 红黑树能够实现自平衡和保持红黑树特征的主要手段是:变色、左旋和右旋
B树可以在内部节点同时存储键和值,因此,把频繁访问的数据放在靠近根节点的地方将会大大提高热点数据的查询效率。这种特性使得B树在特定数据重复多次查询的场景中更加高效。
由于B+树的内部节点只存放键,不存放值,因此,一次读取,可以在内存页中获取更多的键,有利于更快地缩小查找范围。 B+树的叶节点由一条链相连,因此,当需要进行一次全数据遍历的时候,B+树只需要使用O(logN)时间找到最小的一个节点,然后通过链进行O(N)的顺序遍历即可。而B树则需要对树的每一层进行遍历,这会需要更多的内存置换次数,因此也就需要花费更多的时间
前缀索引
, 这样能节省索引空间、提高检索性能最左前缀匹配原则
,组合索引非常重要的原则扩展索引
,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可(in-place ALTER
)经验法则
1. 推荐在设计表结构时将列设置为NOT NULL,除非有特殊需求需要存储NULL值
在mysql中,含有NULL值的列往往使得查询变得更加复杂,在涉及到索引的情况下,NULL值需要额外的处理,可能导致索引失效或者性能下降。你应该用0、一个特殊的值或者一个空串代替空值;
2.离散程度高(值之间的差异程度高)的列放到联合索引的前面
对于联合索引,数据库系统会按照索引的顺序来优化查询计划。如果将离散程度高的列放在前面,可以减小检索的数据集,提高查询性能。
3.索引字段越小越好
数据库的数据存储以页为单位
, 一页存储的数据越多, 一次IO操作获取的数据越大效率越高。
假设已经在数据库表中有一个针对列a的单列索引。现在,需要为列a和列b创建一个联合索引。
-- 假设原来已经有索引名称为idx_a的索引包含列a
-- 如果没有,可以替换为实际的索引名称
-- 1. 删除原有的索引
ALTER TABLE your_table_name DROP INDEX idx_a;
-- 2. 添加新的扩展索引
ALTER TABLE your_table_name ADD INDEX idx_a_b (a, b);
在Mysql中, 当你执行DROP INDEX
语句时,MySQL会从表的元数据中删除索引的定义,但它并不会立即从磁盘上删除索引文件。MySQL会在后台的某个时刻,根据自身的调度和优化策略,清理不再需要的索引文件。这可以包括磁盘空间的回收等操作。
通过ADD INDEX
添加新索引,如果新的索引包含了原索引的所有列,并且顺序相同,MySQL在某些情况下可能会选择重新使用原索引的数据结构,而不是创建全新的索引文件, 这被称为“in-place ALTER”
尽管有可能进行in-place ALTER
,但并不是所有情况下都会发生。具体的优化行为是由MySQL的优化器和底层存储引擎共同决定的。
要注意的是,尽管in-place ALTER可能提高效率,但并不是适用于所有场景。在某些情况下,创建一个全新的索引可能更为合适,尤其是当表的大小较小,资源充足时。
对长字符串建立索引时, 可以只对字符串的前几个字符创建索引, 对于那些长字符串, 但只有前几个字符不同的情况非常有效, 可以节省大量的索引空间, 提升查询性能, 这种方式称为"前缀索引"
或者"前缀长度索引"
ALTER TABLE table_name ADD KEY index_name(column_name(prefix_length));
# 例如:对book表中的type字段的前5个字符建立索引
ALTER TABLE book ADD KEY type_prefix(type(5));
优点:
缺点:
ORDER BY
和GROUP BY
语法:
ALTER TABLE table_name ADD KEY(column_name(prefix_length));
怎么确定prefix_length?
prefix_length这个参数,就是前缀长度的意思,我们可以通过下面的方式来判断设置多少长度的前缀
SELECT COUNT(DISTINCT column_name) / COUNT(*) FROM table_name;
SELECT COUNT(DISTINCT LEFT(column_name, prefix_length)) / COUNT(*) FROM table_name;
我们看个案例
下面以某个测试表为例,数据体量在 100 万以上,表结构如下!
CREATE TABLE `tb_test` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
第一步: 以name作为条件查询,查询一条记录, 查询时间为0.3秒
select * from tb_test where name like '1805.59281427%'
第二步: 为name字段建立前缀索引,找出最合适的prefix_length值。首先,我们大致计算一下name字段全局不同率
, 算下来是99.45%
第三步: 设置不同的prefix_length值,查看对应的数据不重复比例
第四步: 通过对比,我们发现当prefix_length为11,最接近全局区分度,因此可以为name创建一个长度为11的前缀索引,创建索引语句如下:
alter table tb_test add key(name(11));
问题: 如果在字段上建立了前缀长度为5的前缀索引, 当查询条件长度超过5时, 会不会走索引呢?
select * from book where type like 'TPCCB-132%'
步骤一: 对tpye字段建立前缀索引(长度为5), 并添加3条数据
步骤二: 对type字段进行模糊匹配, 匹配内容超出5个字符
explain select * from book where type like 'TPCCB-132%'
前缀索引
过滤出前缀为TPCCB
的数据, 一共有2条, 对应上了rows为2
一个接一个
进行匹配(相当于all扫描), 得到符合TPCCB-132%
的数据接下来, 尝试建立其他几类索引, 来更直观的体会前缀索引的作用
设置合适的前缀长度
空间
和性能
上的缺陷, 设置合适的前缀索引可以达到同样的效果1.创建联合索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边
通常情况下,优先考虑最频繁用于WHERE子句的列。这是因为索引的主要目的之一是加速查询,而最频繁用于WHERE子句的列往往是最经常用于过滤数据的列。通过将这个列放在最左侧,可以最大程度地提高索引的效用,加速查询的速度。
2.如果第一个字段是范围查询需要单独建一个索引
CREATE TABLE example (
column1 INT,
column2 INT,
column3 INT,
PRIMARY KEY (column1, column2, column3)
);
SELECT * FROM example WHERE column1 BETWEEN 100 AND 200 AND column2 = 5;
可以考虑单独为column1建立一个索引, 这并不是说联合索引不起作用,但单独为范围查询的字段建立索引可以更好地支持这种类型的查询
3.使用联合索引时, 会从最左边的列一直向右匹配, 直到遇到范围查询(例如 >
、<
、BETWEEN
、LIKE
)
对(a,b,c,d)
字段建立了索引, 并添加5条数据
执行下面的sql, 查看执行计划
EXPLAIN select * from example where a = 1 and b = 2 and c > 3 and d = 4
rows列
代表Mysql在执行查询时, 预计扫描的行数, 最终显示为2行
原因: a = 1 and b = 2 and c > 3
通过索引过滤出2条记录, 执行d=4
不会走索引了, 所以是对这两条数据进行比对的
4.带头大哥不能死, 中间兄弟不能断
联合索引是指在数据库表中利用多个列来创建一个索引的方式。在查询数据时,可以使用联合索引来加快查询的速度。与单个列的索引相比,联合索引可以更准确地定位到符合条件的数据。
创建联合索引后,数据库系统会按照索引的顺序来存储索引的值,这样就可以在查询时快速定位到需要的数据。联合索引的顺序非常重要,查询时使用的列需要与索引的顺序一致,才能充分发挥索引的作用。
联合索引跟单例索引的性能对比
例如: where name=‘Bob’ and phone = ‘132xxx’
B+Tree会先去比较name=‘Bob’
, 如果name找到, 再去比较phone = ‘132xxx’
如果查询条件中没有name(跳过最左列), 就不知道第一步应该去哪里比对了, 因为建立索引树时name是第一个比较因子
CREATE INDEX idx_name_phoneonuser_innodb(name,phone);
当我们创建一个联合索引的时候,用左边的字段name去查询的时候,也能用到索引,所以单独为name创建一个索引完全没必要。
如果我们创建三个字段的索引index(a,b,c),相当于创建三个索引:index(a)、index(a,b)、index(a,b,c)。
用where b=?
和where b=? and c=?
和where a=? and c=?
是不能使用到索引的。因为不能不用第一个字段,不能中断。
1.使用函数(replace\SUBSTR\CONCAT\sum\count\avg
)、表达式、计算(+ - * /
), 因为当前值改变后就无法与索引存的值匹配山
SELECT * FROM user_innodb where left(name, 3)='张三';-- left函数是一个字符串函数,它返回具有指定长度的字符串的左边部分
2.使用范围查询(!=,<=>,in
)会导致右边列失效。因为二叉树的查找是=查找
,若是一个范围的话无法继续下探
SELECT * FROM user_innodb where name='张三' and age > 22;
3.like以通配符开头("%abc"
), 索引会失效, 变为全表扫描操作, 因为无法判断%代表多少字符
4.少用or, 用它了连接时可能会导致索引失效
因为or
操作符要求同时满足多个条件,数据库无法有效地使用索引来筛选数据。当or
左右查询字段只有一个是索引,该索引失效,只有当or
左右查询字段均为索引时,才会生效。
可以考虑使用其他方法来优化查询性能,例如使用"union"操作符将多个子查询的结果合并,或者重新设计查询语句以避免使用"or"操作符。
5.is null,is not null 无法使用索引
SELECT object_name from t1 WHERE object_name LIKE '%ABC';
解决方法:
CREATE INDEX idx_t1_objectname2 ON t1(reverse(object_name));
SELECT object_name FROM t1 WHERE REVERSE(object_name) LIKE REVERSE('%ABC');
1.ORDER BY的索引优化
SELECT id, name FROM `user` ORDER BY id;
对id
建立索引即可
2.WHERE + ORDER BY的索引优化
SELECT name, sex, age FROM `user` WHERE sex = '男' ORDER BY age;
建立一个联合索引(sex, age)
来实现order by优化
如果sex对应多个值, 就无法利用索引来实现order by的优化
SELECT name, sex, age FROM `user` WHERE sex in ('男', '女') ORDER BY age;
3. WHERE+ 多个字段ORDER BY
SELECT name, sex, age, height FROM `user` WHERE sex = '男' ORDER BY age, height LIMIT 0,10;
建立联合索引(sex, age, height)
实现order by的优化, 比建立(age, height, sex)索引效果要好很多
1.内连接(INNER JOIN)
内连接分为两种: 显式的
和隐式的
select * from a, b where a.id = b.cid
select * from a inner join b on a.id = b.cid
更推荐使用显式内连接
2.全连接(FULL JOIN)
FULL JOIN
关键字返回左表
和右表
中所有的行。
3.union all和union的区别
显示结果不同
对重复结果的处理不同
对排序的处理不同
一条SQL语句的查询结果作为另一条SQL查询语句的条件或者查询结果
多条SQL语句嵌套使用, 内部的SQL查询语句称为子查询
子查询的三种情况
一个值
, 父查询使用:=、 <、 > 等运算符select * from employee where salary=(select max(salary) from employee);
一个数组
, 父查询使用:in 运算符select * from employee where salary in (select salary/10 from employee);
一张虚拟表
, 不能用于where条件,用于select子句中做为子表select * from dept d, (select dept_id, salary from employee where join_date > '2011-1-1') e where e.dept_id = d.id;
select * from student s WHERE exists (select 1 from class c where s.class_id = c.id)
select class_id from student
查询的为c1,c1,c2,c3
, 然后依次拿这些值去class表中对比
not in
和not exists
, 如果查询语句使用not in, 那么内外表都进行全表扫描, 没有用到索引; 而not exists的子查询依然用到比表上的索引, 所以无论哪个表大, not exists都比not in更快对于低性能的SQL的定位, 最重要最有效的方法就是使用执行计划
1.id
id标识查询执行的顺序
id列的值只有两种情况: 数字
、null
2.select_type
select_type | description |
---|---|
SIMPLE | 简单的 select 查询,查询中不包含子查询或者UNION |
PRIMARY | 查询中若包含任何复杂的子部分,最外层查询则被标记为Primary |
SUBQUERY | 在SELECT或WHERE列表中包含了子查询 |
DERIVED | 在FROM列表中包含的子查询被标记为DERIVED(衍生);MySQL会递归执行这些子查询, 把结果放在临时表里 |
UNION | 若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED |
UNION RESULT | 从UNION表获取结果的SELECT |
DEPENDENT SUBQUERY | 在SELECT或WHERE列表中包含了子查询,子查询基于外层 |
UNCACHEABLE SUBQUREY | 无法被缓存的子查询 |
(1) SIMPLE 类型
简单的 select 查询,查询中不包含子查询或者UNION
(2) PRIMARY类型
查询中若包含任何复杂的子部分,最外层查询则被标记为Primary
(3) SUBQUERY类型
在SELECT或WHERE列表中包含了子查询
(4) DERIVED类型
在FROM列表中包含的子查询被标记为DERIVED(衍生);MySQL会递归执行这些子查询, 把结果放在临时表里
(5) UNION类型
若第二个SELECT出现在UNION之后,则被标记为UNION;若UNION包含在FROM子句的子查询中,外层SELECT将被标记为:DERIVED
(6) UNION RESULT类型
从UNION表获取结果的SELECT
(7) DEPENDENT SUBQUERY类型
在SELECT或WHERE列表中包含了子查询,子查询基于外层
3.table
指明是从那个表中获取数据
4.type
值 | 含义 |
---|---|
system | 这是const连接类型的特例,当查询的表只有一行时使用 |
const | 表中有且只有一个匹配 的行时使用,如对主键或唯一索引的查询 ,这是效率最高的链接方式 |
eq_ref | 唯一索引或主键查询 ,对应每个索引建,表中只有一条记录与之匹配 【A表扫描每一行B表只有一行匹配满足】 |
ref | 非唯一索引查找,返回匹配某个单独值的所有行 |
ref_or_null | 类似于ref类型的查询,但是附加了对NULL值列的查询 |
index_merge | 该链接类型表示使用了索引合并优化方法 |
range | 索引范围扫描 ,常见于between、>、< 这样的查询条件 |
index | FULL index Scan全索引扫描 ,同ALL的区别是,遍历的是索引树 |
ALL | FULL TABLE Scan全表扫描 ,这是效率最差的链接方式 |
CREATE TABLE employees (
id INT AUTO_INCREMENT PRIMARY KEY,
department_id INT NOT NULL,
first_name VARCHAR(30) NOT NULL,
last_name VARCHAR(30) NOT NULL,
salary DECIMAL(10, 2) NOT NULL,
hire_date DATE NOT NULL,
INDEX idx_department_id (department_id),
INDEX idx_last_name (last_name)
);
(1) system
当查询仅涉及到一行时,会出现system类型,例如只有一行数据的表。
SELECT * FROM employees LIMIT 1;
(2) const
表示常量, 查询仅涉及到主键或唯一索引的一行时,会出现const类型。
SELECT * FROM employees WHERE id = 1;
(3) eq_ref
当使用唯一索引或主键作为连接条件时,会出现eq_ref类型。
CREATE TABLE departments (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30) NOT NULL
);
SELECT e.*, d.name
FROM employees e
JOIN departments d ON e.department_id = d.id;
(4) ref
当通过非唯一索引
进行连接或查询时,会出现ref类型
SELECT * FROM employees WHERE department_id = 1;
(5) range
当使用范围条件进行查询时,会出现range类型
ALTER TABLE employees ADD INDEX idx_hire_date (hire_date);
SELECT * FROM employees WHERE hire_date BETWEEN '2022-01-01' AND '2022-12-31';
(6) index
当查询中使用到了全索引扫描时,会出现index类型
SELECT last_name FROM employees ORDER BY last_name;
全索引扫描(index)是一种比全表扫描(all)更高效的查询方式, 因为它仅扫描索引, 而非整个表
由于 last_name 列有索引,MySQL 可以直接从索引中获取数据并按照索引的顺序进行排序。这种情况下,索引扫描(index)比全表扫描(all)更高效,因为它只需要扫描索引而不是整个表。
(7) all
当查询需要全表扫描时,会出现all类型
SELECT * FROM employees WHERE salary > 5000;
5.possible_keys
可能使用的索引,注意不一定会使用。查询涉及到的字段上若存在索引,则该索引将被列出来。当该列为 NULL时就要考虑当前的SQL是否需要优化了。
6.key
显示MySQL在查询中实际使用的索引,若没有使用索引,显示为NULL。
7.key_length
索引长度
禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。
将数据库分为主库
和从库
, 主库负责写, 从库负责读
包括冷热数据分离、历史数据归档等