MYISAM和innoDB是最常见的两种存储引擎,特点如下:
MYISAM存储引擎
MYISAM是MySql官方提供默认的存储引擎,其特点是不支持事务,表锁和全文索引,对于一些OLAP(联机分析处理)系统,操作速度快。
每个MYISAM在磁盘上存储成三个文件。文件名和表名相同,扩展名分别为.fm(存储表定义),MDY(MYData,存储数据),MYI(MYIndex,存储索引)。这里特别要注意的是MYISAM不缓存数据文件,只缓存索引文件。
InnoDB存储引擎
InnoDB存储引擎支持事务,主要面向OLTP(联机事务处理过程)方面的应用,其特点是行锁设置,支持外键,并类似于Oracle的非锁定读,即默认情况下读不产生锁。InnoDB将数据放在一张逻辑表空间中(类似Oracle).InnoDB通过·多版本并发控制来获得高并发,实现了ANSI标准的4中隔离级别,默认为Repeatable,使用一种被称为next-key locking的策略避免幻读。
对于表中数据中的存储,InnoDB采用类似Oracle索引组织表Clustered的方式进行存储。InnoDB存储引擎提供了具有提交,回滚和崩溃恢复能力的事务安全。但是对比MYISAM的存储引擎,InnoDB写的处理效率差一些并且会占用更多的磁盘空间以及保留数据和索引。
InnoDB体系架构
- 连接管理与安全验证是什么?
每个客户端都会建立一个服务器连接的线程,服务器会有一个线程池来管理这些连接;如果客户端需要连接到MySql数据库还需要进行验证,包括用户名、密码、主机信息等。- 解析器是什么?
解析器的作用主要是分析查询语句,最终生成解析树:首先解析器会对查询语句的语法进行分析,分析语法是否有问题。还有解析器会查询缓存,如果在缓存中有对应的语句,就返回查询结果不进行接下来的优化执行操作。前提是缓存中的数据没有被修改,当然如果被修改了也会被清除缓存。- 优化器是什么?
有花期的作用主要是对查询语句进行优化操作,包括选择合适的索引,数据的读取方式,包括获取查询的开销信息,统计信息等,这也是为什么图中会有优化器执行存储引擎的箭头。- 执行器是什么?
执行器包括执行语句,返回查询结果,生成执行计划包括与存储引擎的一些处理操作。
- InnoDB存储引擎
InnoDB是事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和瓦匠,InnoDB是默认的MySql引擎。- MYISAM存引擎
MYISAM基于ISAM存储引擎,并对其进行扩展。它在Web,数据仓储和其他应用环境下最常使用的存储引擎之一。MYISAM拥有较高的插入,查询速度,但是不支持事务。- MEMORY存储引擎
MEMORY存储引擎将表中的数据存储到内存中,未查询和引用其他表数据提供快速访问。- NDB存储引擎
DB存储引擎是一个集群存储引擎,类似于Oracle的RAC,但它是Share Nothing的架构,因此能提供更高级别的高可用性和可扩展性。NDB的特点是数据全部放在内存中,因此通过主键查找非常快。
关于NDB,有一个问题需要注意,它的链接(join)操作是在MySql数据层完成,不是在存储引擎完成,这意味着,复杂的join操作需要巨大的网络开销,查询速度会很慢。- Memory(heap)存储引擎
Memory存储引擎(之前称Heap)将表中数据存放在内存中,如果数据库启动或崩溃,数据丢失,因此它非常适合存储历史数据。- Archive存储引擎
正如其名,Archive非常适合存储归档数据,如日志信息。它只支持insert和select操作,其设计的目的是提供高速的插入和压缩功能。- Federated存储引擎
Federated存储引擎不存放数据,它至少指向一台远程MySql数据库服务器上的表,非常类似于Oracle的透明网关。- Maria存储引擎
Maria存储引擎是新开的引擎,其设计目标是用来取代原有的MYISAM存储引擎,从而称为MySql默认的存储引擎。
上述引擎中,InnoDB是事务安全的存储引擎,设计上借鉴了很多Oracle的架构思想,一般而言,在OTLP应用中,InnoDB应该作为核心应用表的首选存储引擎。InnoDB是由第三方的Innobase oy公司开发,现已被Oracle收购,创始人Heikki Tuuri,芬兰人赫尔辛基人,和著名的Linux创始人Linus是校友。
- 原子性(Atomicity):整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停留在某个环节。事务在执行过程中,会被回滚(rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性 (Correspondence):在事务开始之前和事务结束之后,数据库的完整性没有被破坏。
- 隔离性(Isolation):隔离状态执行事务,使他们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一个事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个请求用于统一数据。
- 持久性(Duraability):在事务完成以后,该事务所对数据库所做的更改便持久的保存在数据库之中,并不会回滚。
- 读未提交(Read Uncommited),该隔离级别允许脏读数据,其隔离级别最低,比如事务A和事务B同时进行,事务A在整个执行阶段,会将某数据的值从1开始一致加到10,然后进行事务提交,此时,事务B能够看到这个数据项在事务A操作过程中的所有中间值,而对这一系列的中间值的读取是未授权读取。
- 授权读取也称读已提交(Read Commited),授权读取只允许读取已经提交的数据。比如事务A和事务B同时进行,事务A进行加一操作,此时,事务B无法看到这个数据项在事务A操作过程中的所有中间值,只能看到最终的10.另外,如果说有一个事务C,和事务A进行非常类似的操作,只是事务C是将数据项从10加到20,此时事务B也同样读取到20,即授权读取允许不可重复读。
- 可重复读(Repeatable Read),就是在保证事务处理过程中,多次读取同一个数据,其值都和事务开始时刻是一致的,因此该事务级别禁止不可重复读和脏读取,但是有时候会出现幻读数据。所谓幻读数据,就是指同样的事务操作,在前后两个时间段内执行同一个数据项的读取,可能会出现不一致的结果。
- 串行化,是最严格的事务隔离级别,它要求所有事物被串行化执行性,即事务只能一个接一个的进行处理,不能并发执行。
MySql存储过程是从MySql5.0开始增加的新功能。存储的优点有太多太多了,不过最主要的还是执行效率和SQL代码封装,特别是SQL代码封装功能,如果没有存储过程,在外部程序访问数据库时,要组织很多SQL语句,特别是业务逻辑复杂的时候,一大堆的SQL文件和条件加载在代码中,让人不寒而栗。现有了MySql存储过程,业务逻辑可以封装在存储过程中,这样不仅容易维护,而且执行效率也高。
创建一个交pr_add的mysql存储过程,输入参数a,b返回两数和
drop procedure if exists pr_add;
create procedure pr_add(a int,b int)
begin
declare
c int;
if a is null then set a = 0;
end if;
if b is null then set b = 0;
end if;
set c = a + b;
select c as num;
end;
call pr_add(10,20);
mysql包含对触发器的支持。触发器是一种与表操作有关的数据库对象,当触发器所在表上出现指定事件时,将调用该对象,即表的操作事件触发表上的触发器的执行。
create trigger trigger_name #标识触发器名称,用户自定义
trigger_time #标识触发时机,取值为before 或 after
trigger_event on tb1_name # trigger_event:标识触发事件,取值为insert update或delete
#tb1_name 标识建立触发器的表名,即在哪张表上建立触发器
from each row
trigger_stmt#触发器程序体,可以使一条SQL语句或者用begin或end包含的多条语句
#由此可见可以建立6种触发器即:
#before insert,before update,before delete,
#after insert,after update,after delete
#班级表class(班级号classId,班内学生数stuCount)
#学生表student(学号stuId,所属班级classId)
#创建触发器来使班级表中的班内人数数随着学生的添加自动更新
create trigger tr1_stuInsert after insert
on student for each row
begin
declare c int;
set c = (select studCount from class where classId = new.classId);
update class set stuCount = c+1 where classId = new.classId;
#查看触发器
show trigger triggerName;
#删除触发器
drop trigger [if exists] triggerName
回表就是先通过数据库索引扫描出数据所在的行,再通过行主键id取出索引中未提供的数据,即基于非主键索引的查询需要多扫描一颗索引树。
当查询的字段在二级索引上没有的时候,就需要“回表”在主键上再查一次。
索引是一种数据结构,可以帮助我们快速的进行数据的查找
索引的数据结构和具体存储引擎的实现有关,在mysql中使用较多的索引有Hash索引,B+树索引等,而我们经常使用的InnoDB存储引擎的默认引擎实现为:B+树索引。
- 实现原理
hash索引底层是hash表,进行查找时,调用一次hash函数就可以获取到相应的键值,之后进行回表查询获得实际数据。B+树底层实现是多路平衡查找树,对于每一次的查询就是从根节点出发,查找到叶子结点方可以获得所查键值,然后根据查询判断是否需要回表查询数据。- 不同点
hash索引进行等值查询更快,但是却无法进行范围查询。因为hash索引经过hash函数建立索引之后,索引的顺序与原顺序无法保持一致,不能支持范围查询。而B+树的所有节点皆遵循(左节点小于父节点,右节点大于父节点,多叉树也是类似)天然支持范围。
hash索引不支持使用索引进行排序,原理同上。
hash索引不支持模糊查询以及多列索引的最左前缀匹配,原理也是hash函数的不可预测。
hash索引任何时候都避免了回表查询数据,而B+树在符合某些条件(聚族索引,覆盖索引等)的时候可以通过索引完成查询。
hash索引虽然在等值查询上比较快,但是不稳定,性能不可预测,当某个键值存在大量重复的时候,发生hash碰撞,此时效率可能极差,而B+树的查询效率比较稳定,对于所有的查询都是从根节点到叶子结点,且树的高度较低。
因此,在大多数情况下,直接选择B+树索引可以获得稳定且较好的查询速度,而不需要使用hash索引。
建立索引的时候一般考虑字段的使用频率,经常作为条件进行查询的字段比较合适,如果需要建立联合索引的话,还需要考虑联合索引中的顺序。此处也要考虑其他方面,比如防止过多的索引对表造成太大的压力,这些都和实际的表结构以及查询方式有关。
聚簇索引,二级(辅助)索引
B树索引,hash索引
聚簇索引:将数据存储与索引放在一起,找到索引也就找到了数据
非聚簇索引:将数据与索引分开存储,索引结构的叶子结点指向了数据的对应行
InnoDB有聚簇索引,主键索引就是聚簇索引。
MyIsam没有聚簇索引,因为它的索引和记录行是分开存储的。
MyIsam索引的节点中存储的是数据的物理地址(磁道和扇区),在查找数据时,查找到索引后,根据索引节点中的物理地址,查找到具体的数据内容。
InnoDB的主键索引文件上直接存放该行数据,称为聚簇索引,非主索引指向对主键的引用。
主键索引的叶子结点存的是整行数据。在InnoDB里,主键索引也称为聚簇索引
非主键索引的叶子结点内容是主键的值。在InnoDB里,非主键索引也称为二级索引
记录偷信息的delete——mask标记位设置为1(表示该记录已删除),同时将记录从记录行链表中断开,并加入到垃圾链中,垃圾链表的空间后续可以复用。
优先使用用户定义主键作为主键,如果用户没有定义主键,则选取一个unique键作为主键,如果表中连unique键都没有定义的话,则InnoDB会为表添加一个名为row_id的隐藏列作为主键。所以从上述可以看出:InnoDB存储引擎会为每条记录都田间transaction_id和roll_pointer这两个列,但是row_id是可选的。
因为InnoDB的数据结构是通过聚簇索引组织起来的,如果没有主键的话,通过其他索引回表的时候没法查到相应的数据行。
- redo
保证事务的持久性
MySQL作为一个存储系统,为了保证数据的可靠性,最终得落盘。但是,又为了数据写入的速度,需要引入基于内存的"缓冲池"。其实不止MySQL,这种引入缓冲来解决速度问题的思想无处不在。既然数据是先缓存在缓冲池中,然后再以某种方式刷新到磁盘,那么就存在因宕机导致的缓冲池中的数据丢失,为了解决这种情况下的数据丢失问题,引入了redo log。在其他存储系统,比如Elasticsearch中,也有类似的机制,叫translog。
但是一般讨论数据写入时,在MySQL中,一般叫事务操作,根据事务的ACID特性,如何保证一个事务提交后Durability的保证?而这就是 redo log 的作用。当向MySQL写用户数据时,先写redo log,然后redo log根据"某种方式"持久化到磁盘,变成redo log file,用户数据则在"buffer"中(比如数据页、索引页)。如果发生宕机,则读取磁盘上的 redo log file 进行数据的恢复。从这个角度来说,MySQL 事务的持久性是通过 redo log 来实现的。- undo
实现事务回滚
Undo log是InnoDB MVCC事务特性的重要组成部分。当我们对记录做了变更操作时就会产生undo记录,Undo记录默认被记录到系统表空间(ibdata)中,但从5.6开始,也可以使用独立的Undo 表空间。Undo记录中存储的是老版本数据,当一个旧的事务需要读取数据时,为了能读取到老版本的数据,需要顺着undo链找到满足其可见性的记录。当版本链很长时,通常可以认为这是个比较耗时的操作。大多数对数据的变更操作包括INSERT/DELETE/UPDATE,其中INSERT操作在事务提交前只对当前事务可见,因此产生的Undo日志可以在事务提交后直接删除(谁会对刚插入的数据有可见性需求呢!!),而对于UPDATE/DELETE则需要维护多版本信息,在InnoDB里,UPDATE和DELETE操作产生的Undo日志被归成一类,即update_undo。
log buffer空间不足时
事务提交时
后台线程不停的刷刷刷
正常关闭服务器时
做所谓的checkpoint时
mysql日志一般分为5种
- 错误日志:-log-err(记录启动,运行,停止mysql时出现的问题)
- 二进制日志:-log-bin(记录所有更改数据的语句,还用于复制,恢复数据库用)
- 查询日志:-log(记录建立的客户端连接和执行的语句)
- 慢查询日志:-log-show-queries(记录所有执行超过long-query-time秒的所有查询)
- 更新日志:-log-update(二进制日志已经代替了老的更新日志,更新日志在mysql5.1中不再使用)
- 使用不等于查询
- 列参与了数学运算或者函数
- 在字符串like时左边是通配符
- 当mysql分析全表扫描化使用索引快的时候不使用索引
- 当使用联个索引,前面一个条件为范围查询,后面的即使符合最左前缀原则,也无法使用索引
主键是数据库确保数据行在整张表唯一性的保证,即使业务上本张表没有主键,也建议添加一个自增长的ID列作为主键,设定了主键之后,在后续的删改查的时候可能更加快速以及确保操作数据范围安全。
推荐使用自增ID,不推荐使用UUID
因为在InnoDB存储引擎中,主键索引是作为聚簇索引存在的,也就是说,主键索引的B+树叶子节点上存储了主键索引以及全部的数据,如果主键索引是自增ID,那么只需要不断向后排列即可,如果是UUID,由于到来的ID与原来的大小不确定,会造成非常多的数据插入,数据移动,然后导致产生很多的内存碎片,进而造成插入性能的下降
null值会占用更多的字节,且会在程序中造成很多与预期不符的情况
密码散列,盐,用户省份正等固定长度的字符串应该使用char而不是varchar来存储这样可以节省空间且提高检索效率
三种格式,statement,row和mixed
statement模式下,记录单元为语句,即每一个SQL造成的影响记录,由于SQL的执行是有上下文的,因此在保存的时候需要保存相关的信息,同时还有一些使用了函数子类的语法无法被记录复制
row级别下,记录单元为每一行的改动,基本是可以全部记录下来但是由于很多操作,会导致大量的改动,因此这种模式的文件保存的信息太多,日志量太大
mixed一种折中的方案,普通操作使用statement记录,当无法使用statement的时候使用row
两个方面
数据库层面
例如select * from table where age>20 limit 100000,10;这种查询可以优,需要load 100000数据然后基本上全部丢弃只取10条当然比较慢,可以修改为:select * from table where id (selet id from table where age>20 limit 100000,10);这样虽然也load了十万数据,但是由于索引覆盖,要查询的所有字段都在索引中,所以速度会很很快,同时如果ID连续的话,还可以使用select * from table where id >100000 limit 10,效率也是不错的,优化的可能性有许多种,但是核心思想都一样,就是减少load的数据。
需求阶段减少这种请求,主要是不做类似的需求(直接跳转到几十万也之后的具体某一行,只允许逐页查看或者按照给定的路线走,这样可预测,可缓存)以及防止ID泄露且连续被人恶意攻击
在业务系统中,除了使用主键进行的查询,其他的我都会在测试库上测试其耗时,慢查询的统计主要是由运维来做,会定期将业务中的慢查询返回给我们。
优化三方向
- 首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。
- 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
- 如果对语句的优化已经无法进行,可以考虑表中的数据是否太大,如果是的话可以进行横向或者总想的分表。
存储过程是一些预编译的SQL语句
- 更加直白的理解:存储过程可以说是一个记录集,它是由一些T-SQL语句组成的代码块,这些T-SQL语句代码像一个方法一样实现了一些功能,然后再将这个代码块取一个名字,在用到这个功能的时候调用它就好了。
- 存储过程是一个预编译的代码块,执行效率比较高,一个存储过程替代大量的T-SQL语句,可以降低网络通信量,提供通信效率,可以一定程度上确保数据安全。
第一范式:每个列都不可以再分
第二范式:非主键列完全依赖与主键,而不能是依赖于主键的一部分
第三范式:非主键列只能依赖主键,不依赖于其他主键
表记录太少
经常插入,删除,修改的表
数据重复且分布平均的表字段
经常和主字段一块查询但主字段索引值比较多的表字段
表分区是根据一定规则,将数据库中的一张表分解成多个更小的,容易管理的部分。从逻辑上看,只有一张表,但是底层却是有多个物理分区组成。
分表:指的是通过一定规则,将一张表分解成多张不同的表
分表和分区区别在于:分区从逻辑上只讲一张表,而分表则是将一张表分解成多张表
- 存储更多数据。分区表的数据可以分布在不同的物理设备上,从而高效地利用多个硬件设备。和单个磁盘或者文件系统相比,可以存储更多的数据
- 优化查询。在where语句中包含分区条件时,可以只扫描一个或多个分区表来提高查询效率,涉及sum或count语句时,也可以在多个分区上进行处理,最后会总结过。
- 分区表更容易维护。
- 避免某些特殊的瓶颈。
MySql InnoDB存储引擎,实现的是基于多版本的并发控制协议–mmvc。
mvvc的好处:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大地增加了系统的并发性能,现阶段几乎所有的RDBMS都支持了mvvc.
LBCC:lock-Based Concurrency Control,基于锁的并发控制
MVVC:Multi-Version Concurrency Control
基于多版本的并发控制协议。纯粹基于锁的并发机制并发量低,mvvc是在基于锁的并发控制上的改进,主要是在读操作上提高了并发量
- 快照读:读取的是记录的可见版本,不用加锁
当前读:读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事物不会再并发修改这条记录。
优点
- 当在许多线程中访问不同的行时只存在少量锁定冲突
- 回滚时只有少量的更改
- 可以常时间锁定单一的行
缺点- 比页级或表级锁定占用更多的内存
- 当在表的大部分中使用时,比页级或表级锁定速度慢,因为你必须获取更多的锁
- 如果你在大部分数据上经常进行group by操作或者必须经常扫描整个类,比其它锁定明显慢很多
- 用高级别锁定,通过支持不同的类型锁定,你也可以容易的调节应用程序,因为其锁成本小于行及锁定。
- 开启查询缓存,优化查询
- explain你的select查询,这样可以帮你分析你的查询语句或是表结构的性能检测
- 当只要一行数据时使用limit 1,MySql数据库引擎会找到一条数据后停止搜索,而不是继续完后查找下一条符合记录的数据
- 为搜索字段建立索引
- 使用enum 而不是varchar。如果你有一个字段,比如“性别”,“国家”等,你知道这些字段的取值是有限而固定的,那么就应该使用enum而不是varchar
- Prepared Statementss 很像存储过程,是一种运行在后台的SQL语句集合,可以使用Prepared Statements获得很多好处,无论是性能问题还是安全问题
- 垂直分表
- 选择正确的存储引擎
- key是数据库的物理结构,它包含两层意义和作用,一是约束,二是索引。包括primary key,unique key,foreign key等
- index是数据库的物理结构,它是辅助查询的,它创建时会在另外的表空间以一个类似目录的结构存储。索引要分类的话,分为前缀索引,全文索引等
truncate和delete只删除数据,不删除表结构,drop删除表结构,并且释放所占的空间。
删除数据的速度drop>truncate>delete
delete属于DML语句,需要事务管理,commit之后才生效。drop和truncate属于DDL语句,操作立刻生效,不可回滚。适用场合:当不需要该表时,使用的drop;当你仍需保留该表,但是要删除所有记录时,用truncate;当你要删除部分记录时用delete。
主:binlog线程–记录下所有改变了数据库数据的语句,放入master上的binlog中。
从:io线程–在使用start slave之后,负责从master上拉去binlog内容,放进自己的relay log中
从:SQL执行线程–执行relay log中的语句;
MyIsam表把自增主键的最大ID记录到数据文件中
InnoDB表把自增主键的最大ID记录到内存中
distinct在所有列上转换为group by,并与order by子句结合使用
交叉连接又叫笛卡尔积,它是指适用任何条件,直接将一个表的所有记录和另一个表中的所有记录一一匹配
内连接则是只有条件的交叉连接,根据某个条件筛选出符合条件的记录,不符合条件的记录不会出现结果接中,即内连接只连接匹配的行
外连接其结果集中不仅包含符合连接条件的行,而且还会包括左表,右表或两个表中的所有数据行,这三种情况依次称之为左外连接,右外连接和全外连接
左外连接也称左连接,左表为主表,左表中的所有记录都会出现在结果集中,对于那些左右表中并没有匹配的记录,仍然要显示,右边对应的那些字段以null来填充。
右外连接也称右连接,右表为主表,右表中的所有记录都会出现在结果集中。
左右连接可以互换,mysql目前还不支持全外连接。