1.说说自己对于 MySQL 常见的两种存储引擎:MyISAM与InnoDB的理解
关于二者的对比与总结:
1.count运算上的区别:因为MyISAM缓存有表meta-data(行数等),因此在做COUNT(*)时对于一个结构很好的查询是不需要消耗多少资源的。而对于InnoDB来说,则没有这种缓存。
2.是否支持事务和崩溃后的安全恢复: MyISAM 强调的是性能,每次查询具有原子性,其执行数度比InnoDB类型更快,但是不提供事务支持。但是InnoDB 提供事务支持事务,外部键等高级数据库功能。 具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe (ACID compliant))型表。
3.是否支持外键: MyISAM不支持,而InnoDB支持。
MyISAM更适合读密集的表,而InnoDB更适合写密集的的表。 在数据库做主从分离的情况下,经常选择MyISAM作为主库的存储引擎。 一般来说,如果需要事务支持,并且有较高的并发读取频率(MyISAM的表锁的粒度太大,所以当该表写并发量较高时,要等待的查询就会很多了),InnoDB是不错的选择。如果你的数据量很大(MyISAM支持压 缩特性可以减少磁盘的空间占用),而且不需要支持事务时,MyISAM是最好的选择。
2.数据库索引了解吗?
聚集索引与非聚集索引
我们平时建表的时候都会为表加上主键, 在某些关系数据库中, 如果建表时不指定主键
,数据库会拒绝建表的语句执行。如果定义了主键,InnoDB会自动使用主键来创建聚集索
引。如果没有定义主键,InnoDB会选择一个唯一的非空索引代替主键。如果没有唯一的非
空索引,InnoDB会隐式定义一个主键来作为聚集索引。)
MyISAM:
B+Tree叶节点存放的是数据记录的地址,在检索的时候,先找到索引对应的数据记录的
地址,再根据地址读取相应的数据记录,这种查找方式被称为“非聚集索引”。
InnoDB:
它的主键索引是聚集索引,即主键和行记录放在同一个叶节点,找到了主键也就找到
了行记录;而它的非主键索引,或者说是辅助索引,是非聚集索引,跟MyISAM引擎的
非聚集索引不同的是,MyISAM叶节点保存的是地址,而InnoDB是主键,InnoDB非聚
集索引的索引文件和数据文件分开存储,索引文件的叶节点只保存主键,在查找时,
要先找到叶节点中的主键,再根据主键去主索引文件查找详细行记录;因此,在设计
表的时候,主键字段不宜过长。
为什么要给表加上主键?
事实上, 一个加了主键的表,并不能被称之为「表」。
一个没加主键的表,它的数据无序的放置在磁盘存储器上,一行一行的排列的很整齐
如果给表上了主键,那么表在磁盘上的存储结构就由整齐排列的结构转变成了树状结构,
也就是上面说的「平衡树」结构
换句话说,就是整个表就变成了一个索引,是所谓的「聚集索引」。
这就是为什么一个表只能有一个主键,一个表只能有一个「聚集索引」,因为主键的作
用就是把「表」的数据格式转换成「索引(平衡树)」的格式放置。
为什么索引能提高查询速度?
其中树的所有结点(底部除外)的数据都是由主键字段中的数据构成,也就是通常我们指定主键的id字段。最下面部分是真正表中的数据。
假如我们执行一个SQL语句: select * from table where id = 1256;
首先根据索引定位到1256这个值所在的叶结点,然后再通过叶结点取到id等于1256的数据行。
这里不讲解平衡树的运行细节, 但是从上图能看出,树一共有三层, 从根节点至叶节点只
需要经过几次查找就能得到结果。如下图
很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过 “目录” 就可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn))
其实底层结构就是B+树,B+树作为树的一种实现,能够让我们很快地查找出对应的记录。
假如一张表有一亿条数据 ,需要查找其中某一条数据,按照常规逻辑, 一条一条的
去匹配的话,最坏的情况下需要匹配一亿次才能得到结果,用大O标记法就是O(n)最坏
时间复杂度,这是无法接受的,而且这一亿条数据显然不能一次性读入内存供程序使用。
因此, 这一亿次匹配在不经缓存优化的情况下就是一亿次IO开销,以现在磁盘的IO能
力和CPU的运算能力, 有可能需要几个月才能得出结果。
如果把这张表转换成平衡树结构(一棵非常茂盛和节点非常多的树),假设这棵树有10
层,那么只需要10次IO开销就能查找到所需要的数据, 速度以指数级别提升。
n是记录总树,底数是树的分叉数,结果就是树的层次数。换言之,查找次数是以树的
分叉数为底,记录总数的对数,用公式来表示就是
用程序来表示就是Math.Log(100000000,10),100000000是记录数,10是树的分叉数(真实环境下分叉数远不止10),
结果就是查找次数,这里的结果从亿降到了个位数。因此,利用索引会使数据库查询有惊人的性能提升。
为什么加索引后会使写入、修改、删除变慢?
然而, 事物都是有两面的, 索引能让数据库查询数据的速度上升, 而使写入数据的
速度下降,原因很简单的,
因为平衡树这个结构必须一直维持在一个正确的状态, 增删改数据都会改变平衡树
各节点中的索引数据内容,破坏树结构, 因此,在每次数据改变时,
DBMS必须去重新梳理树(索引)的结构以确保它的正确,这会带来不小的性能开销,
也就是为什么索引会给查询以外的操作带来副作用的原因。
讲完聚集索引 , 接下来聊一下非聚集索引, 也就是我们平时经常提起和使用的常规索引。
非聚集索引和聚集索引一样, 同样是采用平衡树作为索引的数据结构。索引树结构中各
节点的值来自于表中的索引字段。
假如给user表的name字段加上索引 , 那么索引就是由name字段中的值构成,在数据改
变时, DBMS需要一直维护索引结构的正确性。如果给表中多个字段加上索引 , 那么就
会出现多个独立的索引结构,每个索引(非聚集索引)互相之间不存在关联。
如下图
每次给字段建一个新索引, 字段中的数据就会被复制一份出来, 用于生成索引。
因此, 给表添加索引,会增加表的体积, 占用磁盘存储空间。
非聚集索引和聚集索引的区别在于, 通过聚集索引可以查到需要查找的数据,
而通过非聚集索引可以查到记录对应的主键值 ,
再使用主键的值通过聚集索引查找到需要的数据,如下图
不管以任何方式查询表, 最终都会利用主键通过聚集索引来定位到数据, 聚集索引
(主键)是通往真实数据所在的唯一路径。
然而, 有一种例外可以不使用聚集索引就能查询出所需要的数据, 这种非主流的
方法 称之为「覆盖索引」查询。
也就是平时所说的复合索引或者多字段索引查询。 文章上面的内容已经指出, 当为
字段建立索引以后, 字段中的内容会被同步到索引之中。
如果为一个索引指定两个字段, 那么这个两个字段的内容都会被同步至索引之中。
先看下面这个SQL语句
//建立索引
create index index_birthday on user_info(birthday);
//查询生日在1991年11月1日出生用户的用户名
select user_name from user_info where birthday = ‘1991-11-1’
这句SQL语句的执行过程如下
首先,通过非聚集索引index_birthday查找birthday等于1991-11-1的所有记录的主键ID值
然后,通过得到的主键ID值执行聚集索引查找,找到主键ID值对就的真实数据(数据行)存储的位置
最后, 从得到的真实数据中取得user_name字段的值返回, 也就是取得最终的结果
什么情况下要同时在两个字段上建索引?
我们把birthday字段上的索引改成双字段的覆盖索引
create index index_birthday_and_user_name on user_info(birthday,
user_name);
这句SQL语句的执行过程就会变为
通过非聚集索引index_birthday_and_user_name查找birthday等于1991-11-1的叶
节点的内容,然而,
叶节点中除了有user_name表主键ID的值以外, user_name字段的值也在里面, 因此
不需要通过主键ID值的查找数据行的真实所在,
直接取得叶节点中user_name的值返回即可。 通过这种覆盖索引直接查找的方式, 可
以省略不使用覆盖索引查找的后面两个步骤,
大大的提高了查询性能,如下图
最左前缀原则
MySQL中的索引可以以一定顺序引用多列,这种索引叫作联合索引。
如User表的name和city加联合索引就是(name,city)o而最左前缀
原则指的是,如果查询的时候查询条件精确匹配索引的左边连续一列
或几列,则此列就可以 被用到。如下:
select * from user where name=xx and city=xx ; //可以命中索引
select * from user where name=xx ; // 可以命中索引
select * from user where city=xx; // 无法命中索引
这里需要注意的是,查询的时候如果两个条件都用上了,但是顺序不同,如
city= xx and name =xx ,那么现在的查询引擎会自动优化为匹配联合
索引的顺序,这样是能够命中索引的.
由于最左前缀原则,在创建联合索引时,索引字段的顺序需要考虑字段值去
重之后的个数,较多的放前面。
ORDERBY子句也遵循此规则。
注意避免冗余索引
冗余索引指的是索引的功能相同,能够命中 就肯定能命中 ,那么 就是冗
余索引如(name,city )和(name )这两个索引就是冗余索引,能够命
中后者的查询肯定是能够命中前者的 在大多数情况下,都应该尽量扩展
已有的索引而不是创建新索引。
MySQLS.7 版本后,可以通过查询 sys 库的schemal_r dundant_indexes
表来查看冗余索引
Mysql如何为表字段添加索引
1.添加PRIMARY KEY(主键索引)
ALTER TABLE `table_name` ADD PRIMARY KEY ( `column` )
2.添加UNIQUE(唯一索引)
ALTER TABLE `table_name` ADD UNIQUE ( `column` )
3.添加INDEX(普通索引)
ALTER TABLE `table_name` ADD INDEX index_name ( `column` )
4.添加FULLTEXT(全文索引)
ALTER TABLE `table_name` ADD FULLTEXT ( `column`)
5.添加多列索引
ALTER TABLE `table_name` ADD INDEX index_name ( `column1`, `column2`, `column3` )
当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:
当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:
1.限定数据的范围: 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。;
2.读/写分离: 经典的数据库拆分方案,主库负责写,从库负责读;
3.垂直分区: 根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。简单来说垂直拆分是指数据表列的拆分, 把一张列比较多的表拆分为多张表。 如下图所示,这样来说大家应该就更容易理解了。垂直拆分的优点: 可以使得行数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。垂直拆分的缺点: 主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;
4.水平分区: 保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。
水平拆分可以支撑非常大的数据量。 水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多 个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。水平拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的 数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 水平拆分最好分库 。水平拆分能够 支持非常大的数据量存储,应用端改造也少,但 分片事务难以解决 ,跨界点Join性能较差,逻辑复杂。
《Java工程师修炼之道》的作者推荐 尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。
下面补充一下数据库分片的两种常见方案:
1.客户端代理: 分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。 当当网的 Sharding-
JDBC 、阿里的TDDL是两种比较常用的实现。
2.中间件代理: 在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。 我们现在谈的 Mycat
、360的Atlas、网易的DDB等等都是这种架构的实现。
事务隔离级别(图文详解)
什么是事务?
事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就 是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩
溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要 失败。
事物的特性(ACID)
1.原子性: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
2.一致性: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
3.隔离性: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
4.持久性: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
并发事务带来的问题
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操 作)。并发虽然是必须的,但可能会导致以下的问题。
脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个 事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢
失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的 数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就 好像发生了幻觉一样,所以称为幻读。
不可重复度和幻读区别:
不可重复读的重点是修改,幻读的重点在于新增或者删除。
例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导 致A再读自己的工资时工资变为 2000;这就是不可重复读。
例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。
事务隔离级别
SQL 标准定义了四个隔离级别:
READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
READ-COMMITTED(读取已提交): 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
REPEATABLE-READ(可重复读): 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE(可串行化): 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读)。我们可以通过SELECT @@tx_isolation; 命令来查看
这里需要注意的是:与 SQL 标准不同的地方在于InnoDB 存储引擎在 REPEATABLE-READ(可重读)事务隔离级别下使用的是Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库系统(如 SQL Server)是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要
求,即达到了 SQL标准的SERIALIZABLE(可串行化)隔离级别。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内 容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读)并不会有任何性能损失。
InnoDB 存储引擎在 分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。