✏️作者:银河罐头
系列专栏:MySQL
“种一棵树最好的时间是十年前,其次是现在”
索引是一种特殊的文件,包含着对数据表里所有记录的引用指针。可以对表中的一列或多列创建索引,
并指定索引的类型,各类索引有各自的数据结构实现。
索引相当于书的目录,可以提高查的速度。但是索引也提高了增删改的开销,因为进行增删改会需要调整已经创建好的索引目录,降低增删改的速度。索引还提高了空间的开销。
增删改中包含查,总体来看查的频率高于增删改,所以索引对于提高数据库的性能有很大的帮助。
学生表里没有创建索引但是自带一个索引
因为主键要保证非空且不重复,所以每次插入数据都要检查是否已经存在,有索引可以提高查找速度。
unique和foreign key也会自动创建索引
创建索引
根据名字这一列来创建索引
创建索引,最好是在创建表的时候就创建索引。如果在表已经有很多记录的时候再创建索引是危险操作(会消耗大量的磁盘IO,花很长时间,在这个时间段里数据库无法正常使用)。
如果对性别或者大学生的年龄这样的列创建索引,这样无法提高查找速度,因为太多重复。
查看索引
删除索引
删除索引也可能消耗大量的磁盘IO
SQL是通过数据库的执行引擎来执行的,涉及一些优化操作。执行引擎会自动评估哪种方案是成本最低速度最快的。
可以使用explain关键字显示出查询过程中具体使用索引的情况。
哈希表查找元素时间复杂度是O(1),但是哈希表不适合做数据库的索引。原因是哈希表只能比较两个元素是否相等,不能进行’>‘、’<'这样的范围查询
二叉搜索树的时间复杂度是O(1),AVL/红黑树的时间复杂度是O()
(二叉意味着元素个数越多,树的高度越高,查询时元素的比较次数越多,数据库进行比较是要读硬盘的。)
其中一种典型代表是B树。
比较次数没有减少,一个节点上可能需要比较多次,但是读写硬盘的次数减少了,每个节点都保存在硬盘上。(读硬盘比读内存要慢几千倍)
对B树做进一步的改进,又引入了B+树。
索引的数据结构就是B+树
1.B+树也是一个N叉搜索树,每个节点上可能包含N个key,N个key 划分出N个区间,最后一个key相当于最大值。
2.父元素的key会在子元素中以最大值形式重复出现。(这样一来叶子节点就包含了所有数据的全集)
3.会把叶子节点用类似链表的方式首尾相连
B+树的优点:
1.作为一个N叉搜索树,高度降低使得读硬盘IO次数减少(这一点同B树)
2.更适合做范围查询
3.所有的查询都要落在叶子节点上,无论查询哪个元素,查询的比较次数很均衡
4.由于所有的key都会在叶子结点上体现,所以非叶子节点上不用存表的真实记录(不必存数据行),只需要把所有数据行放在叶子结点上即可,非叶子结点只用存索引列的值(比如id)。
由于非叶子结点只存了索引列的值,没有存数据行,这样意味着叶子节点占用的空间大大降低,有可能放进内存中缓存,这样进一步降低了硬盘IO
有的表不只是主键索引,还有别的非主键列也有索引(比如前面的把name作为索引)。
这种情况会构造另外一棵B+树,这个B+树非叶子节点存这个索引列里的Key(比如一些name)。到了叶子节点这一层,存的不是完整的数据行,而是存主键id。
使用主键列来查询,只要查一次B+树就可以。
使用非主键列的索引来查询,就要先查询索引列的B+树,再查一遍主键列的B+树。
“再查一遍"这个操作是"回表”。
当前B+树这个结构,只是针对MySQL的InnoDB(最主流使用的一种存储引擎)这个数据库引擎 里面典型使用的数据结构。不同的数据库,不同的引擎,里面存储数据的数据结构还可能存在差异。
数据库的存储引擎,其实就是实现了数据库如何在硬盘上组织数据。
事务指逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败。
事务的四大特性:原子性、一致性、持久性、隔离性
举例1:转账
1)给1的余额-100
2)给2的余额+100
举例2:下订单
1)商品库存-1
2)订单表+1
这些操作是要么全部成功,要么全部失败(执行中途出错的情况)。
执行中间出错了,就让一条都不执行,意思是恢复成执行之前的样子,看起来像是一条都没执行。
回滚(rollback):把执行过的操作逆向恢复回去。
数据库会把执行的每步操作都记录下来,如果某个操作出错就会把事务中前面的操作进行回滚,根据之前进行的操作进行逆操作。
这些操作有很大开销,不可能无限保存,最多是把正在执行的这个事务保存。
开启事务之后,中间这些sql不会被立刻执行,而是先攒着,等commit再一起执行。(原子性)
事务执行前/后数据都是合法状态。
硬盘断电数据不丢失,事务产生的修改都会写入硬盘。即使程序重启/主机重启/掉电,事务都可以正常工作,保证修改是持久生效的。
一个数据库服务器,同时执行多个事务的时候,事物之间的"相互影响程度"。
一个服务器可以同时给多个客户端提供服务,这多个客户端彼此之间是一个"并发执行"的关系
如果隔离性越高,就意味着事务之间的并发程度越低,执行效率越慢,数据准确性越高;
如果隔离性越低,就意味着事务之间的并发程度越高,执行效率越快,数据准确性越低。
事例:假设张三在写一份文件,李四在旁边看,此时张三的写操作和李四的读操作是2个事务,是完全并发的,没有任何限制。等李四走了张三把文件内容改了,那么李四读到的就是错误数据,这就是"脏读"。
解决"脏读"问题就要提高隔离性,降低并发性,具体来说就是给这里的写操作加锁。张三写文件时加锁,写文件过程中李四不能读,等张三写完了之后李四才可以看。整体上花的时间更多了,李四读到的数据准确性提高了。
事例:有了"写加锁",等张三写完文件之后,李四再读文件,而在李四读文件过程中,张三又修改了文件内容,此时李四读文件读了一半发现文件内容自动变了。
在一个事物中,连续两次读到的数据不一致,这就是"不可重复读"
解决:给"读操作"也加锁,李四读文件的时候张三不得修改。
这样使得这两个事务的并发程度进一步降低,隔离性进一步提高,执行效率进一步降低,数据准确性进一步提高
事例:李四读文件时,张三不能修改李四正在读的那份文件,但是张三去新增/删除另外的文件,导致李四发现他读的文件内容没变,但是他发现文件的数量变了。
在同一个事务中,两次读到的结果集不同。这就是"幻读"
解决:串行化,彻底舍弃并发。李四读文件时,张三什么都不能做。
MySQL提供了4个隔离级别
隔离级别 | 描述 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
read uncommitted | 不做任何限制,事物之间可以随意并发执行。并发程度最高,隔离程度最低。 | ✔ | ✔ | ✔ |
read committed | 对写操作加锁,并发程度降低,隔离性提高 | ❌ | ✔ | ✔ |
repeatable read | (默认档位)对写和读都加锁,并发程度进一步降低,隔离性进一步提高。 | ❌ | ❌ | ✔ |
serializable | 严格串行化,并发程度最低,隔离性最高,执行速度最慢 | ❌ | ❌ | ❌ |
通过mysql的配置文件来调整隔离级别