Mysql 优化知识点(二)

什么是Mysql ?
Mysql是一种关系型数据库,在JAVA企业开发中非常常用, 因为Mysql是开源免费的,并且方便扩展。阿里巴巴数据库系统也大量用到了Mysql ,因此他的稳定性是有保障的。

一.事务的四个特征(ACID)

事务具有四个特征: 原子性,隔离性, 持久性,一致性
原子性:事务中包含的操作 要么都做,要么都不做;
一致性:事务执行的结果必须是使数据库从一个执行状态变更为另一个一致性状态,比如数据库系统运行中发生了故障,有些事务尚未完成被迫中断 这些未完成的事务对数据库做的修改 就会导致数据库状态处于不一致性的状态;;
隔离性:一个事务的执行不会能被其他事务的干扰,即一个事务内部的操作及使用的数据对其他并发事务是隔离的, 并发执行的各个事务之间不能相关干扰~
持久性:也称为永久性, 指一个事务一旦提交,它对数据库中的数据的改变就应该是永久的,接下来的其他操作或者故障不应该对运行的结果有任何影响。

二 . mysql 四种隔离级别**

  1. Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
    最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。简而言之,它是在每个度的数据行上加上共享锁,在这个级别,可能导致大量的超时现象和锁竞争。

  2. Repeatable read (可重复读):可避免脏读、不可重复读的发生。

  Mysql 默认的隔离级别是可重复读(Repeatable read) *

它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行数。不过理论上,这会导致另一个棘手的问题 幻读。
InnoDB和Falcon存储引擎通过多个版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

  1. Read committed (读已提交):可避免脏读的发生。

  2. Read uncommitted (读未提交):最低级别,任何情况都无法保证。

a.脏读:事务A修改了某条数据,事务B读取了事务A 修改的该条数据,然后事务A由于某些原因回滚了,这样子事务B之前读取的该条数据 和回滚后的该数据就不一致了,此时事务B读取的该条数据就是脏数据。 (一个事务读取到了另一个事务未提交的数据)。

b.不可重复读:事务A读取了某条数据, 事务A没有提交,然后事务B修改了该条数据,这时事务A再次读取该条数据,此时事务A两次读取的数据不一致。 (一个事务多次读取某条数据,当事务A再次以相同的条件查询数据时,会发现两次查询出来的数据不一致)。

c.幻读:事务A按照一定的条件读取了该表中的一些数据,然后事务B向该表插入了一些满足事务A查询条件的数据,当事务A再次用相同的条件查询数据库时,会发现多出来一些数据,就好像产生了幻觉一样。

mysql 默认的隔离级别是可重复读

三.为什么用自增列作为主键**

1、如果我们定义了主键(PRIMARY KEY),那么InnoDB会选择主键作为聚集索引。
如果没有显式定义主键,则InnoDB会选择第一个不包含有NULL值的唯一索引作为主键索引。
如果也没有这样的唯一索引,则InnoDB会选择内置6字节长的ROWID作为隐含的聚集索引(ROWID随着行记录的写入而主键递增,这个ROWID不像ORACLE的ROWID那样可引用,是隐含的)。

2、数据记录本身被存于主索引(一颗B+Tree)的叶子节点上,这就要求同一个叶子节点内(大小为一个内存页或磁盘页)的各条数据记录按主键顺序存放
因此每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,如果页面达到装载因子(InnoDB默认为15/16),则开辟一个新的页(节点);

3、如果表使用自增主键,那么每次插入新的记录,记录就会顺序添加到当前索引节点的后续位置,当一页写满,就会自动开辟一个新的页;

4、如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置
此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销
同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。

===========================================================================================

四. 为什么 MySQL 索引要使用 B+树而不是其它树形结构?

InnoDB一棵B+树可以存放多少行数据?这个问题的简单回答是:约2千万。
InnoDB存储引擎也有自己的最小储存单元——页(Page),一个页的大小是16K。innodb的所有数据文件(后缀为ibd的文件),他的大小始终都是16384(16k)的整数倍。数据表中的数据都是存储在页中的,所以一个页中能存储多少行数据呢?假设一行数据的大小是1k,那么一个页可以存放16行这样的数据。

因为B树不管叶子节点还是非叶子节点,都会保存数据,这样导致在非叶子节点中能保存的指针数量变少(有些资料也称为扇出)。指针少的情况下要保存大量数据,只能增加树的高度,导致IO操作变多,查询性能变低;

一个二叉查找树是由n个节点随机构成,所以,对于某些情况,二叉查找树会退化成一个有n个节点的线性链。和顺序查找差不多。显然这个二叉树的查询效率就很低,因此若想最大性能的构造一个二叉查找树,需要这个二叉树是平衡的(这里的平衡从一个显著的特点可以看出这一棵树的高度比上一个输的高度要大,在相同节点的情况下也就是不平衡),从而引出了一个新的定义-平衡二叉树AVL。AVL树是带有平衡条件的二叉查找树,一般是用平衡因子差值判断是否平衡并通过旋转来实现平衡,左右子树树高不超过1,和红黑树相比,它是严格的平衡二叉树,平衡条件必须满足(所有节点的左右子树高度差不超过1)。不管我们是执行插入还是删除操作,只要不满足上面的条件,就要通过旋转来保持平衡,而旋转是非常耗时的,由此我们可以知道AVL树适合用于插入删除次数比较少,但查找多的情况。

1、 B+树的磁盘读写代价更低:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。

2、B+树的查询效率更加稳定:由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。

3、由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。

======================================================================================

五. [MySQL聚集索引和非聚集索引]

MySQL的Innodb存储引擎的索引分为聚集索引和非聚集索引两大类,理解聚集索引和非聚集索引可通过对比汉语字典的索引。汉语字典提供了两类检索汉字的方式,第一类是拼音检索(前提是知道该汉字读音),比如拼音为cheng的汉字排在拼音chang的汉字后面,根据拼音找到对应汉字的页码(因为按拼音排序,二分查找很快就能定位),这就是我们通常所说的字典序;第二类是部首笔画检索,根据笔画找到对应汉字,查到汉字对应的页码。拼音检索就是聚集索引,因为存储的记录(数据库中是行数据、字典中是汉字的详情记录)是按照该索引排序的;笔画索引,虽然笔画相同的字在笔画索引中相邻,但是实际存储页码却不相邻,这是非聚集索引。

聚集索引
索引中键值的逻辑顺序决定了表中相应行的物理顺序。
聚集索引确定表中数据的物理顺序。聚集索引类似于电话簿,后者按姓氏排列数据。 聚集索引对于那些经常要搜索范围值的列特别有效。使用聚集索引找到包含第一个值的行后,便可以确保包含后续索引值的行在物理相邻。例如,如果应用程序执行 的一个查询经常检索某一日期范围内的记录,则使用聚集索引可以迅速找到包含开始日期的行,然后检索表中所有相邻的行,直到到达结束日期。这样有助于提高此 类查询的性能。同样,如果对从表中检索的数据进行排序时经常要用到某一列,则可以将该表在该列上聚集(物理排序),避免每次查询该列时都进行排序,从而节 省成本。

============================================================================================

六. Mysql

1.哈希索引的优势:
等值查询,哈希索引具有绝对优势
(前提是:没有大量重复键值,如果大量重复键值时,哈希索引的效率很低,因为存在所谓的哈希碰撞问题。)

哈希索引不适用的场景:

  1. 不支持范围查询
  2. 不支持索引完成排序
  3. 不支持联合索引的最左前缀匹配规则
    通常,B+树索引结构适用于绝大多数场景,像下面这种场景用哈希索引才更有优势:
    在HEAP表中,如果存储的数据重复度很低(也就是说基数很大),对该列数据以等值查询为主,没有范围查询、没有排序的时候,特别适合采用哈希索引,例如这种SQL:

2.什么情况下应不建或少建索引
1、表记录太少
2、经常插入、删除、修改的表
3、数据重复且分布平均的表字段,假如一个表有10万行记录,有一个字段A只有T和F两种值,且每个值的分布概率大约为50%,那么对这种表A字段建索引一般不会提高数据库的查询速度。
4、经常和主字段一块查询但主字段索引值比较多的表字段

3.什么是表分区?
表分区,是指根据一定规则,将数据库中的一张表分解成多个更小的,容易管理的部分。从逻辑上看,只有一张表,但是底层却是由多个物理分区组成。

表分区与分表的区别
分表:指的是通过一定规则,将一张表分解成多张不同的表。比如将用户订单记录根据时间成多个表。
分表与分区的区别在于:分区从逻辑上来讲只有一张表,而分表则是将一张表分解成多张表。

表分区有什么好处?
1、存储更多数据。分区表的数据可以分布在不同的物理设备上,从而高效地利用多个硬件设备。和单个磁盘或者文件系统相比,可以存储更多数据
2、优化查询。在where语句中包含分区条件时,可以只扫描一个或多个分区表来提高查询效率;涉及sum和count语句时,也可以在多个分区上并行处理,最后汇总结果。
3、分区表更容易维护。例如:想批量删除大量数据可以清除整个分区。
4、避免某些特殊的瓶颈,例如InnoDB的单个索引的互斥访问,ext3问价你系统的inode锁竞争等。

分区表的限制因素

  1. 一个表最多只能有1024个分区
  2. MySQL5.1中,分区表达式必须是整数,或者返回整数的表达式。在MySQL5.5中提供了非整数表达式分区的支持。
  3. 如果分区字段中有主键或者唯一索引的列,那么多有主键列和唯一索引列都必须包含进来。即:分区字段要么不包含主键或者索引列,要么包含全部主键和索引列。
  4. 分区表中无法使用外键约束
  5. MySQL的分区适用于一个表的所有数据和索引,不能只对表数据分区而不对索引分区,也不能只对索引分区而不对表分区,也不能只对表的一部分数据分区。

如何判断当前MySQL是否支持分区?
命令:show variables like '%partition%' 运行结果:

have_partintioning 的值为YES,表示支持分区。

MySQL支持的分区类型有哪些?

  1. RANGE分区: 这种模式允许将数据划分不同范围。例如可以将一个表通过年份划分成若干个分区
  2. LIST分区: 这种模式允许系统通过预定义的列表的值来对数据进行分割。按照List中的值分区,与RANGE的区别是,range分区的区间范围值是连续的。
  3. HASH分区 :这中模式允许通过对表的一个或多个列的Hash Key进行计算,最后通过这个Hash码不同数值对应的数据区域进行分区。例如可以建立一个对表主键进行分区的表。
  4. KEY分区:上面Hash模式的一种延伸,这里的Hash Key是MySQL系统产生的。

5.关于MVVC
MySQL InnoDB存储引擎,实现的是基于多版本的并发控制协议——MVCC (Multi-Version Concurrency Control)
:与MVCC相对的,是基于锁的并发控制,Lock-Based Concurrency Control
MVCC最大的好处:读不加锁,读写不冲突。在读多写少的OLTP应用中,读写不冲突是非常重要的,极大的增加了系统的并发性能,现阶段几乎所有的RDBMS,都支持了MVCC。

  1. LBCC:Lock-Based Concurrency Control,基于锁的并发控制
  2. MVCC:Multi-Version Concurrency Control
    基于多版本的并发控制协议。纯粹基于锁的并发机制并发量低,MVCC是在基于锁的并发控制上的改进,主要是在读操作上提高了并发量。

在MVCC并发控制中,读操作可以分成两类:

  1. 快照读 (snapshot read):读取的是记录的可见版本 (有可能是历史版本),不用加锁(共享读锁s锁也不加,所以不会阻塞其他事务的写)
  2. 当前读 (current read):读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录

6.行级锁定的优点:
1、当在许多线程中访问不同的行时只存在少量锁定冲突。
2、回滚时只有少量的更改
3、可以长时间锁定单一的行。

行级锁定的缺点:

  1. 比页级或表级锁定占用更多的内存。
  2. 当在表的大部分中使用时,比页级或表级锁定速度慢,因为你必须获取更多的锁。
  3. 如果你在大部分数据上经常进行GROUP BY操作或者必须经常扫描整个表,比其它锁定明显慢很多。
  4. 用高级别锁定,通过支持不同的类型锁定,你也可以很容易地调节应用程序,因为其锁成本小于行级锁定。

7.MySQL优化

  1. 开启查询缓存,优化查询
  2. explain你的select查询,这可以帮你分析你的查询语句或是表结构的性能瓶颈。EXPLAIN 的查询结果还会告诉你你的索引主键被如何利用的,你的数据表是如何被搜索和排序的
  3. 当只要一行数据时使用limit 1,MySQL数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据
  4. 为搜索字段建索引
  5. 使用 ENUM 而不是 VARCHAR。如果你有一个字段,比如“性别”,“国家”,“民族”,“状态”或“部门”,你知道这些字段的取值是有限而且固定的,那么,你应该使用 ENUM 而不是VARCHAR
  6. Prepared StatementsPrepared Statements很像存储过程,是一种运行在后台的SQL语句集合,我们可以从使用 prepared statements 获得很多好处,无论是性能问题还是安全问题。
    Prepared Statements 可以检查一些你绑定好的变量,这样可以保护你的程序不会受到“SQL注入式”攻击
  7. 垂直分表
  8. 选择正确的存储引擎

8.key和index的区别

  1. key 是数据库的物理结构,它包含两层意义和作用,一是约束(偏重于约束和规范数据库的结构完整性),二是索引(辅助查询用的)。包括primary key, unique key, foreign key 等
  2. index是数据库的物理结构,它只是辅助查询的,它创建时会在另外的表空间(mysql中的innodb表空间)以一个类似目录的结构存储。索引要分类的话,分为前缀索引、全文本索引等;

9.Mysql 中 MyISAM 和 InnoDB 的区别有哪些?

区别:

  1. InnoDB支持事务,MyISAM不支持
    对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
  2. InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败;
  3. InnoDB是聚集索引,数据文件是和索引绑在一起的,必须要有主键,通过主键索引效率很高。
    但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此主键不应该过大,因为主键太大,其他索引也都会很大。
    而MyISAM是非聚集索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
  4. InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
  5. Innodb不支持全文索引,而MyISAM支持全文索引,查询效率上MyISAM要高;

如何选择:

  1. 是否要支持事务,如果要请选择innodb,如果不需要可以考虑MyISAM;
  2. 如果表中绝大多数都只是读查询,可以考虑MyISAM,如果既有读写也挺频繁,请使用InnoDB
  3. 系统奔溃后,MyISAM恢复起来更困难,能否接受;
  4. MySQL5.5版本开始Innodb已经成为Mysql的默认引擎(之前是MyISAM),说明其优势是有目共睹的,如果你不知道用什么,那就用InnoDB,至少不会差。

10.数据库表创建注意事项
1、字段名及字段配制合理性

  • 剔除关系不密切的字段;
  • 字段命名要有规则及相对应的含义(不要一部分英文,一部分拼音,还有类似a.b.c这样不明含义的字段);
  • 字段命名尽量不要使用缩写(大多数缩写都不能明确字段含义);
  • 字段不要大小写混用(想要具有可读性,多个英文单词可使用下划线形式连接);
  • 字段名不要使用保留字或者关键字;
  • 保持字段名和类型的一致性;
  • 慎重选择数字类型;
  • 给文本字段留足余量;
    2、系统特殊字段处理及建成后建议
  • 添加删除标记(例如操作人、删除时间);
  • 建立版本机制;
    3、表结构合理性配置
  • 多型字段的处理,就是表中是否存在字段能够分解成更小独立的几部分(例如:人可以分为男人和女人);
  • 多值字段的处理,可以将表分为三张表,这样使得检索和排序更加有调理,且保证数据的完整性!
    4、其它建议
  • 对于大数据字段,独立表进行存储,以便影响性能(例如:简介字段);
  • 使用varchar类型代替char,因为varchar会动态分配长度,char指定长度是固定的;
  • 给表创建主键,对于没有主键的表,在查询和索引定义上有一定的影响;
  • 避免表字段运行为null,建议设置默认值(例如:int类型设置默认值为0)在索引查询上,效率立显;
  • 建立索引,最好建立在唯一和非空的字段上,建立太多的索引对后期插入、更新都存在一定的影响(考虑实际情况来创建);

============================================================================================

七. 解决数据一致性的悲观锁和乐观锁

1.悲观锁方案

每次获取商品时,对该商品加排他锁。也就是在用户A获取获取 id=1 的商品信息时对该行记录加锁,期间其他用户阻塞等待访问该记录。悲观锁适合写入频繁的场景。

begin;
select * from goods where id = 1 for update;
update goods set stock = stock - 1 where id = 1;
commit;

2.乐观锁方案
每次获取商品时,不对该商品加锁。在更新数据的时候需要比较程序中的库存量与数据库中的库存量是否相等,如果相等则进行更新,反之程序重新获取库存量,再次进行比较,直到两个库存量的数值相等才进行数据更新。乐观锁适合读取频繁的场景。

#不加锁获取 id=1 的商品对象
select * from goods where id = 1

begin;
#更新 stock 值,这里需要注意 where 条件 “stock = cur_stock”,只有程序中获取到的库存量与数据库中的库存量相等才执行更新
update goods set stock = stock - 1 where id = 1 and stock = cur_stock;
commit;
image.png

======================================================================

你可能感兴趣的:(Mysql 优化知识点(二))