MySQL的事务、存储引擎及索引结构
1、MySQL的底层结构图
2、MySQL的特性及事务
2.1 MySQL的四大特性
- 原子性:事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
- 一致性:事务开始前和结束后,数据库的完整性约束没有被破坏
- 隔离性:个事务的执行不能被其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持久化:指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响
2.2 MySQL事务的并发问题
- 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
- 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
- 幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
2.3 事务的隔离级别
默认的隔离级别为:可重复读
查看MySQL当前的隔离级别:
select @@transaction_isolation;
设置全局的隔离级别(修改之后重新连接MySQL查看):
set global transaction isolation level 隔离级别名称;
设置会话隔离级别:
set session transaction isolation level 隔离级别名称;
设置一次操作的级别:
set transaction isolation level 隔离级别名称;
说明:
- 事务隔离级别为读提交时,写数据只会锁住相应的行
- 事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
- 事务隔离级别为串行化时,读写数据都会锁住整张表
- 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
3、存储引擎
3.1 InnoDB(默认)
InnoDB 是一种兼顾高可靠性和高性能的通用存储引擎,在 MySQL 5.5 之后,InnoDB 是默认的 MySQL 存储引擎。
InnoDB 的特性主要有:
- DML 操作(增、删、改)遵循 ACID(事务安全表) 模型,支持事务。
- 行级锁,提高并发访问性能。
- 支持外键(FOREIGN KEY)约束,保证数据的完整性和正确性。
InnoDB的逻辑存储结构:
3.2 MyISAM
MyISAM不支持事务,也不支持外键,其优势是访问的速度,对事务完整性没有要求或者以SELECT,INSERT为主的应用基本上都可以使用这个引擎来创建表,每个MyISAM引擎在磁盘上存储成3个文件,其文件名都和表名相同,但扩展名分别是
.frm(存储定义)
.MYD(MYDdata,存储数据)
.MYI(MYIndex,存储索引)
数据文件和索引文件可以放置在不同的目录,平均分布IO,获得更快的速度,要制定索引文件和数据文件的路径,需要在创建表的时候通过DATA DIRECTORY和INDEX DIRECTORY语句指定,也就是说不同Myisam表的索引文件和数据文件可以放置到不同的路径下,文件路径需要的是绝对路径,并且具有访问权限。
注意:MyISAM类型的表可能会损坏,原因会有很多种,损坏后的表不能访问,会提示需要修复或者访问后返回错误结果,可以使用CHECK TABLE语句来检查Myisam表的健康,并用REPAIR TABLE 语句修复一个损坏的MyISAM表。
MyISAM的特点:
- 不支持事务
- 不支持外键
- 访问速度快
- 不支持行锁,支持表锁
3.3 Memory
Memory存储引擎是在内存中来创建表,每个memory表只实际对应一个磁盘文件格式是.frm. 该引擎的表访问非常得快,因为数据是放在内存中,且默认是hash索引,其速度比使用B型树(BTREE)索引快。如果我们需要使用B型树索引,可以在创建索引时选择使用。但服务关闭,表中的数据就会丢失掉。每个基于Memory存储引擎的表实际对应一个磁盘文件,该文件的文件名与表名相同,类型为frm类型。该文件只存储表的结构,而其数据文件,都是存储在内存中的,这样有利于对数据的快速的处理,提高整个表的处理效率。
Memory表的大小是受到限制的,表的大小主要取决于2个参数,分别是max_rows和max_heap_table_size。其中,max_rows可以在创建表时指定,max_heap_table_size的大小默认为16MB,可以按需要进行扩大。
Memory的特性:
- 访问速度快
- 支持Hash索引
- 支持表锁
3.4 三种存储引擎的对比
功能 | InnoDB | MyISAM | Memory |
---|---|---|---|
存储限制 | 64TB | 256TB | RAM |
事务 | 支持 | 不支持 | 不支持 |
全文索引 | 不支持 | 支持 | 不支持 |
树索引 | 支持 | 支持 | 支持 |
Hash索引 | 不支持 | 不支持 | 支持 |
数据缓存 | 支持 | N/A | 不支持 |
外键 | 支持 | 不支持 | 不支持 |
4、索引结构
4.1 B-Tree
B-Tree的特点:
- B树是一种完全平衡的2-3树的结构,即一个节点可以派生2或3个节点,通过树的合并和分裂,最终达到绝对平衡性。
- 树的每个节点代表索引的行记录的数据。
- 关键字匹配后,会根据递归的二分法查询子节点。
- 数据区有两种存储方案:存储行记录的位置、存储行记录的索引数据。
- 树形结构在数据的增加、删除过程中需要进行重新调整,使其达到平衡。因此,对于使用较少的字段、或者多变的字段,一般不建议使用索引。
4.2 B+Tree(MySQL)
B+Tree是B-Tree的改进版本,同时也是数据库索引索引所采用的存储结构。数据都在叶子节点上,并且增加了顺序访问指针,每个叶子节点都指向相邻的叶子节点的地址。相比B-Tree来说,进行范围查找时只需要查找两个节点,进行遍历即可。而B-Tree需要获取所有节点,相比之下B+Tree效率更高。
B+Tree的特点:
- 只有叶子节点上才会存储数据,根节点和子节点存储的为主键索引值
- 在叶子节点上形成一个单向链表,每个节点都有指针指向下一个节点
与B-Tree的区别:
- B+树的IO效率高于B-树。
- 基于索引的表扫描性能高于B-树。
- 排序能力强于B-树。
- 基于索引的查询B+树更趋于稳定。
4.3 Hash
哈希索引(hash index)基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码(hash code),哈希码是一个较小的值,并且不同键值的行计算出来的哈希码也不一样。哈希素引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。
Hash索引的特点:
- 只支持等值匹配,不支持范围匹配
- 不支持排序操作
5、SQL的性能分析
5.1 查看SQL语句的执行频率
show global status like 'Com_______';#7个下划线
5.2 慢日志查询
慢查询日志记录了所有执行时间超过指定参数(long_ query_ time, 单位:秒,默认10秒)的所有SQL语句的日志,它可以记录所有执行效率很低的SQL语句,可以针对这些语句进行优化。 MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf) 中进行配置,所需命令行如下:
- 查看慢日志是否开启:show variables like ‘slow_query_log’;
- MySQL的配置文件(/etc/my.cnf) 中添加:slow_query_log=1 long_query_time=2,开启慢日志且设置长查询时间为2s;
- 直接使用命令 set global slow_query_log = ON; 开启慢日志。
查看慢SQL执行:SHOW PROCESSLIST;
5.3 SQL性能分析
Show profiles是MySql用来分析当前会话SQL语句执行的资源消耗情况,可以用于SQL的调优测量。
Show profiles默认状态下是关闭的,执行**set profiling=true;**打开状态
可以用show profile;
查看最近执行的SQL语句执行情况
其中Query_ID为每条执行SQL的编号,Duration为执行时间,Query为SQL语句。
使用SQL命令show profile for query 6;
可以查询每个Query_ID对应的SQL语句的资源耗费情况
其中Status表示Query_ID为6的那条SQL语句执行过程中经历的所有状态,Duration表时每个状态期间的耗费时间,执行该SQL的总时间即为所有的Duration加起来的总和。
5.4 explain执行计划
使用方法:explain + SQL语句
字段解释:
- id:select 查询序列号。id相同,执行顺序由上至下;id不同,id值越大优先级越高,越先被执行
- select_type:查询数据的操作类型,其值如下:
– simple:简单查询,不包含子查询或 union – primary:包含复杂的子查询,最外层查询标记为该值
– subquery:在 select 或 where 包含子查询,被标记为该值
– derived:在 from 列表中包含的子查询被标记为该值,MySQL 会递归执行这些子查询,把结果放在临时表
–union:若第二个 select 出现在 union 之后,则被标记为该值。若 union 包含在 from 的子查询中,外层
select 被标记为 derived
– union result:从 union 表获取结果的 select- table:显示该行数据是关于哪张表
- partitions:匹配的分区
- type:表的连接类型,其值,性能由高到底排列如下:
– system:表只有一行记录,相当于系统表
– const:通过索引一次就找到,只匹配一行数据
– eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常用于主键或唯一索引扫描
– ref:非唯一性索引扫描,返回匹配某个单独值的所有行。用于=、< 或 > 操作符带索引的列
– range:只检索给定范围的行,使用一个索引来选择行。一般使用between、>、<情况
– index:只遍历索引树
– ALL:全表扫描,性能最差
注:前5种情况都是理想情况的索引使用情况。通常优化至少到range级别,最好能优化到 ref- possible_keys:指出 MySQL 使用哪个索引在该表找到行记录。如果该值为 NULL,说明没有使用索引,可以建立索引提高性能
- key:显示 MySQL 实际使用的索引。如果为 NULL,则没有使用索引查询
- key_len:表示索引中使用的字节数,通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好 显示的是索引字段的最大长度,并非实际使用长度
- ref:显示该表的索引字段关联了哪张表的哪个字段
- rows:根据表统计信息及选用情况,大致估算出找到所需的记录或所需读取的行数,并不一定准确
- filtered:返回结果的行数占读取行数的百分比,值越大越好
- extra:包含不合适在其他列中显示但十分重要的额外信息,常见的值如下:
– using filesort:说明 MySQL 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取。出现该值,应该优化 SQL
– using temporary:使用了临时表保存中间结果,MySQL 在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by。出现该值,应该优化 SQL
– using index:表示相应的 select 操作使用了覆盖索引,避免了访问表的数据行,效率不错
– using where:where 子句用于限制哪一行
– using join buffer:使用连接缓存
– distinct:发现第一个匹配后,停止为当前的行组合搜索更多的行
注意:出现前 2 个值,SQL 语句必须要优化。
6、索引失效的情况
1. 联合索引,违背最左侧法则(联合索引的第一列必须有,且不能跳过索引字段)
2. 联合索引,有>、<符号时,使用>、<符号索引字段的右侧索引字段失效
3. 在索引列上进行计算操作
4. 字符串类型的字段不加引号‘’
5. 模糊查询,尾部模糊查询,索引不会失效;头部模糊查询,索引则会失效
6. 用or查询时,or所连接的所有字段都有索引时,索引不会失效,只要有一个字段没有索引,则索引失效
7. 查询结果绝大部分满足查询索引条件时,索引失效,如:表中共有100条数据,使用索引条件查询出来的结果有80条时
7、SQL优化方向
7.1 索引优化
7.1.1 指定索引
- 联合索引和其他索引冲突时,use index(index_name) 使用这个索引
- ignore index(index_name) 忽略这个索引
- force index(index_name) 必须使用这个索引
7.1.2 减少回表查询
- 尽量减少使用select * (会有回表查询),尽量使用select index_name(减少回表查询)
- 如果有多个查询条件,可以将查询条件改成联合索引,而不是单列索引
7.1.3 前缀索引
针对字符串类型(varchar、text)的字段,将字符串的前缀加索引(create index index_name on table (字段名(N))
)
7.2 插入优化
- 一条SQL语句批量插入
- 手动提交事务
- 主键顺序插入避免页分裂
- 大批量插入数据,可以使用load指令
7.3 主键优化
- 尽量降低主键的长度,避免二级索引查询时增加磁盘IO
- 尽量不要使用UUID或者身份证号作为主键
- 不要修改主键,避免修改索引结构
7.4 order by优化
- 尽量使用顺序索引进行排序
- 多字段排序时,也遵循最左前缀法则
- 尽量使用覆盖索引
- 如果避免不了,可以增加排序缓冲区大小,sort_buffer_size(默认256K)
7.5 group by优化
- 多字段分组时,也遵循最左前缀法则
- 可以使用索引来提高效率