斗志斗勇之mysql

通过执行计划优化SQL:EXPLAIN select * from user_info;

idselect_typetablepartitionstypepossible_keyskeykey_lenrefrowsfilteredextra
id:select 查询的序列号,包含一组数字,表示查询中执行select子句或操作表的顺序,分三种情况:
1.id相同,执行顺序由上到下
2.id不同,如果是子查询,id的序号会递增,id值越大优先级越高,越先被执行
3.id相同不同,同时存在
select_type

simple:简单的select查询,查询中不包含子查询或者union


primary: 查询中若包含任何复杂的子部分,最外层查询则被标记为primary

subquery:在select或where列表中包含了子查询

derived: 在from列表中包含的子查询被标记为derived(衍生),MySQL会递归执行这些子查询,把结果放在临时表里。

union:若第二个select出现在union之后,则被标记为union;若union包含在from子句的子查询中,外层select将被标记为derived。

union result: 从union表获取结果的select

table查询的表
type

all,index,range,ref,eq_ref,const,system,null

从最好到最差依次是:system>const>eq_ref>ref>range>index>all

possible_keys显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
key

实际使用的索引。如果为null,则没有使用索引。查询中若使用了覆盖索引,则该索引仅出现在key中

key为null,也就是索引失效

key_len表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度。在不损失精确性的情况下,长度越短越好。
key_len显示的值为索引字段的最大可能长度,并非实际使用长度。key_len是根据表定义计算而得,不是通过表内检索得到的。
ref
rows

根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。

查询的得到的数据总条数

filtered
extra
MySQL执行sql的过程

1.客户端发送一条查询给服务器。

2. 服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段。

3.服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划。

4. MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询。

5.将结果返回给客户端。

sql执行过程

1.客户端把sql语句发送给服务器端执行

2.sql语句解析:

   

1.查询高速缓存:

服务器不会直接去查数据库,而是把sql 字符串转换为ASCII字节码,接着ASCII被传递给一个hash函数,并返回一个hash值,然后服务器进程将到缓存中shared pool 中library cache去查找是否存储相同的hash值。如果存在,服务器就会将它的结果返回。

2.sql语句合法性检查

3.sql语言含义检查

4.获得对象解析锁

5.数据访问权限的核对

6.确定最佳执行计划

3.绑定变量赋值

4.语句执行

5.提取数据

三大范式

数据库三大范式:

第一范式:数据库表的每一个字段都是不可分割的。

第二范式:数据库表中的非主属性只依赖于主键

第三范式:不存在非主属性对关键字的产地函数依赖关系。

事务相关-事务基本特性ACID分别是:
原子性

Atomicity,原子性指的是一个事务中的操作要么全部成功,要么全部失败,对于一个事务来说不可能只执行其中的部分操作,不会结束在中间某个环节。

数据库通过回滚操作完成原子性,当发生错误异常或者显式的执行rollback语句时需要把数据还原到原先的模样,用undo log来进行回滚。

一致性

Consistency,一致性指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱, 假设中间sql执行过程中系统崩溃A也不会损失100块,因为事务没有提交,修改也就不会保存到数据库。

在事务开始之前和事务结束以后,数据库的完整性没有被破坏。

隔离性

Isolation,隔离性指的是一个事务的修改在最终提交前,对其他事务是不可⻅的。

多个事务之间是独立的,不相互影响的。

隔离性是要管理多个并发读写请求的访问顺序。 这种顺序包括串行或者是并行

持久性

Durability,指的是一旦事务提交,所做的修改就会永久保存到数据库中。事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。系统崩溃修改的数据也不会丢失。

MySQL的数据存储机制:MySQL的表数据是存放在磁盘上的,存取都要经历磁盘IO,即使是使用SSD磁盘IO也是非常消耗性能的。为了提升性能InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘数据页的映射,可以当做缓存来使用:

读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取在放入缓冲池;

写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中

缓冲池的措施虽然在性能方面带来了质的飞跃,但是它也带来了新的问题,当MySQL系统宕机,断电的时候可能会丢数据!!!因为我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们需一种机制需要存一下已提交事务的数据,为恢复数据使用。而这个机制就是redo log

Mysql隔离级别:
1.read uncommitted

读未提交,事务中的修改,即使没有提交,对其他事务也都是可见的。会造成脏读(dirty read),由于不能防止脏读和不可重复读,因此实际应用中一般很少使用

因为读不会加任何锁,所以写操作在读的过程中修改数据,所以会造成脏读。好处是可以提升并发处理性能,能做到读写并行

优点:读写并行,性能高
缺点:造成脏读

2.read commited

读已提交,大多数数据库系统的默认级别都是read commited。一个事务只能看见已经提交的事务所做的修改。也称为不可重复读事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。

该级别可以防止脏读,这也是大多数情况下推荐的值,InnoDB在read commited,使用排它锁,读取数据不加锁而是使用了MVCC机制。即采用了读写分离机制。但是该级别会产生不可重读以及幻读问题。

这跟 read commited级别下的MVCC机制有关系,在该隔离级别下每次 select的时候新生成一个版本号,所以每次select的时候读的不是一个副本而是不同的副本。

在每次select之间有其他事务更新了我们读取的数据并提交了,那就出现了不可重复读

3.repeatable read

可重复读,repeatable read该级别可以防止脏读和不可重复读。无法解决幻读问题。保证了在同一个事务中多次读取同样的记录的结果是一样的。[当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行 phantom row],InnoDB 和XtraDB 存储引擎通过多版本并发控制(MVCC )解决了幻读问题,也可以采用读写锁实现。

一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。即使在多次查询之间有新增的数据满足该查询,这些新增的记录也会被忽略

mysql 默认隔离级别:可重复读

MVCC实现:因为多次读取只生成一个版本,读到的自然是相同数据。

优点:读写并行

缺点:实现的复杂度高

读写锁实现:只要没释放读锁,在次读的时候还是可以读到第一次读的数据。

优点:实现起来简单

缺点:无法做到读写并行

4.serializable  

可串行化,serializable 是最高的隔离级别。它通过强制事务串行执行,避免了幻读问题。serializable 会在读取的每一行数据上都加锁,导致大量的超时和锁竞争的问题。实际开发中很少用。

所有的事务依次逐个执行,事务之间完全不会产生干扰。

概念:

脏读概念:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据(一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据)。

不可重复读概念:两个事务,事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致(在一个事务内多次读取的结果不一样)。

幻读概念:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。

事务的传播特性-propagation 指定该事务的传播特性:
required

required 是 Spring 默认的事务传播类型。如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

当前方法存在事务时,子方法加入该事务。此时父子方法共用一个事务,无论父子方法哪个发生异常回滚,整个事务都回滚。即使父方法捕捉了异常,也是会回滚。而当前方法不存在事务时,子方法新建一个事务。

supports如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续执行
mandatory如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常
requires_new

创建一个新的事务,如果当前存在事务,则把当前事务挂起。

无论当前方法是否存在事务,子方法都新建一个事务。此时父子方法的事务是独立的,它们都不会相互影响。但父方法需要注意子方法抛出的异常,避免因子方法抛出异常,而导致父方法回滚。

not_supported以非事务的方式运行,如果当前存在事务,则把当前事务挂起
never以非事务的方式运行,如果当前存在事务,则抛出异常
nested

如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则取值等价于required

当前方法存在事务时,子方法加入在嵌套事务执行。当父方法事务回滚时,子方法事务也跟着回滚。

当子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。如果捕捉了异常,那么就不回滚,否则回滚。
 

可以看到 nestedrequired 的区别在于:父方法与子方法对于共用事务的描述是不一样的,required 说的是共用同一个事务,而 nested 说的是在嵌套事务执行。

这一个区别的具体体现是:

1.在子方法事务发生异常回滚时,父方法有着不同的反应动作。对于  REQUIRED  来说,无论父子方法哪个发生异常,全都会回滚。

而 NESTED  则是:父方法发生异常回滚时,子方法事务会回滚。而子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。

当前方法存在事务时,子方法加入在嵌套事务执行。当父方法事务回滚时,子方法事务也跟着回滚。当子方法事务发送回滚时,父事务是否回滚取决于是否捕捉了异常。如果捕捉了异常,那么就不回滚,否则回滚。

事务失效

原因:

1.事务方法访问修饰符非public

解决方法:修改修饰符为public

2.在方法中自身(this)调用的话可能会失效

解决方法: 自己注入自己,用注入的实例调用或者新建一个类去调用

3.数据库本身不支持事务

解决方法:修改事务

4.@Transational注解方法抛出的异常不是spring事务支持的异常,导致事务失效

解决方法:1.指定spring支持的事务类型

                  2.抛出RuntimeException运行时异常或者spring事务支持的异常

5.@Transational注解所在的类没有被spring管理,导致事务失效

解决方法:加上Service注解或使用其他能注册成spring Bean的方式或注解

5.catch掉异常之后,没有再次抛出事务异常,导致事务失效

解决方法:捕获异常后再次抛出

6.数据源没有配置事务管理器,导致事务失效

7.传播类型不支持事务,导致事务失效

8.多线程调用,导致事务失效

MySQL

事务实现原理

MySQL事务实现原理【根据undo log 和 redo log 和数据库锁以及MVCC实现】:

可靠性:数据库要保证当insert或update操作时抛异常或者数据库crash的时候需要保障数据的操作前后的一致,想要做到这个,则需要知道我修改之前和修改之后的状态,实现机制分别是undo log和redo log。

并发处理:当多个并发请求过来,并且其中有一个请求是对数据修改操作的时候会有影响,为了避免读到脏数据,所以需要对事务之间的读写进行隔离,至于隔离到啥程度得看业务系统的场景了,实现这个就得用MySQL 的隔离级别。

binlog:1.binlog 是mysql server层记录的日志;2.binlog属于逻辑日志,binlog日志用于记录所有更新且提交了数据的所有语句,它描述数据变更。

 binlog作用:

1.恢复时能够最大可能的更新数据库,因为二进制日志包含备份后进行的所有更新。

2.在主复制服务器上记录所有将发送给从服务器的语句。

redo log:redo log叫做重做日志,属于物理日志,是物理变更。是innodb 存储引擎层的日志,用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件(redo log),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中。系统可以根据redo log的内容,将所有数据恢复到最新的状态。记录的是新数据的备份,在事务提交前,只要将redo log 持久化即可,不需要将数据持久化。当系统崩溃时,虽然数据没有持久化,但是redo log已经持久化。

redo log 和缓存的区别:

1.redo log 的存储是顺序存储,而缓存同步是随机操作。

2.缓存同步是以数据页为单位的,每次传输的数据大小大于redo log。

redo log 的作用:mysql 为了提升性能不会把每次的修改都实时同步到磁盘,而是会先存到Boffer Pool(缓冲池)里头,把这个当作缓存来用。然后使用后台线程去做缓冲池和磁盘之间的同步。

为了防止redo log还没来的同步的时候宕机或断电了,导致丢部分已提交事务的修改信息,引入了redo log来记录已成功提交事务的修改信息,并且会把redo log持久化到磁盘,系统重启之后在读取redo log恢复最新数据。

总结:redo log是用来恢复数据的保障,已提交事务的持久化特性。

undo log:undo log 叫做回滚日志,是为了实现事务的原子性,在innodb中,还用undolog实现多版本并发控制。用于记录数据被修改前的信息。和redo log 相反,重做日志记录数据被修改后的信息。undo log主要记录的是数据的逻辑变化,为了在发生错误时回滚之前的操作,需要将之前的操作都记录下来,然后在发生错误时才可以回滚。

1.每条数据变更(insert/update/delete)操作都伴随一条undo log的生成,并且回滚日志必须先于数据持久化到磁盘上


2.所谓的回滚就是根据回滚日志做逆向操作,比如delete的逆向操作为insert,insert的逆向操作为delete,update的逆向为update等。

undo log 作用:undo log 记录事务修改之前版本的数据信息,因此假如由于系统错误或者rollback操作而回滚的话可以根据undo log的信息来进行回滚到没被修改前的状态。

总结:undo log是用来回滚数据的用于保障未提交事务的原子性。数据库通过回滚操作完成原子性,当发生错误异常或者显式的执行rollback语句时需要把数据还原到原先的模样,用undo log来进行回滚。

binlog 和 redolog关系:binlog 和 redolog  两者都是记录了某些操作的日志自然有些重复,但记录的格式不同。

MySQL的数据存储机制:MySQL的表数据是存放在磁盘上的,存取都要经历磁盘IO,即使是使用SSD磁盘IO也是非常消耗性能的。为了提升性能InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘数据页的映射,可以当做缓存来使用:

读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取在放入缓冲池;

写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中

缓冲池的措施虽然在性能方面带来了质的飞跃,但是它也带来了新的问题,当MySQL系统宕机,断电的时候可能会丢数据!!!因为我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们需一种机制需要存一下已提交事务的数据,为恢复数据使用。而这个机制就是redo log

mysql锁技术以及MVCC-------------------------------------------------------------------------------------

mysql锁技术:

当有多个请求来读取表中的数据时可以不采取任何操作,但是多个请求里有读请求,又有修改请求时必须有一种措施来进行并发控制。不然很有可能会造成不一致。

读写锁:解决上述问题,需用两种锁的组合来对读写请求进行控制,这两种锁被称为:

共享锁(shared lock):"读锁",读锁是可以共享的,或者说多个读请求可以共享一把锁读数据,不会造成阻塞。

排他锁(exclusive lock):"写锁",写锁会排斥其他所有获取锁的请求,一直阻塞,直到写入完成释放锁。

总结:
1.通过读写锁,可以做到读读可以并行,但是不能做到写读,写写并行;
2.事务的隔离性就是根据读写锁来实现的

MVCC:多版本并发控制,InnoDB的 MVCC ,是通过在每行记录的后面保存两个隐藏的列来实现的。这两个列,一个保存了行的创建时间,一个保存了行的过期时间,当然存储的并不是实际的时间值,而是系统版本号。他的主要实现思想是通过数据多版本来做到读写分离。从而实现不加锁读进而做到读写并行。

MySQL的数据存储机制:MySQL的表数据是存放在磁盘上的,存取都要经历磁盘IO,即使是使用SSD磁盘IO也是非常消耗性能的。为了提升性能InnoDB提供了缓冲池(Buffer Pool),Buffer Pool中包含了磁盘数据页的映射,可以当做缓存来使用:

读数据:会首先从缓冲池中读取,如果缓冲池中没有,则从磁盘读取在放入缓冲池;

写数据:会首先写入缓冲池,缓冲池中的数据会定期同步到磁盘中;

缓冲池的措施虽然在性能方面带来了质的飞跃,但是它也带来了新的问题,当MySQL系统宕机,断电的时候可能会丢数据!!!因为我们的数据已经提交了,但此时是在缓冲池里头,还没来得及在磁盘持久化,所以我们需一种机制需要存一下已提交事务的数据,为恢复数据使用。而这个机制就是redo log

MVCC在mysql中的实现依赖的是undo log与read view

undo log :undo log 中记录某行数据的多个版本的数据。

read view :用来判断当前版本数据的可见性

原子性:使用 undo log ,从而达到回滚

持久性:使用 redo log,从而达到故障后恢复

隔离性:使用锁以及MVCC,运用的优化思想有读写分离,读读并行,读写并行

一致性:通过回滚,以及恢复,和在并发环境下的隔离做到一致性。

spring

事务实现

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。通过 Spring AOP 切面技术,在合适的地方开启事务,接着在合适的地方提交事务或回滚事务,从而实现了业务编程层面的事务操作。

对于纯JDBC操作数据库,想要用到事务,可以按照以下步骤进行:

1.获取连接 Connection con = DriverManager.getConnection()

2.开启事务con.setAutoCommit(true/false);

3.执行CRUD

4.提交事务/回滚事务 con.commit() / con.rollback();

5.关闭连接 conn.close();

使用Spring的事务管理功能后,我们可以不再写步骤 2 和 4 的代码,而是由Spirng 自动完成。

1.配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。

2.spring 在启动的时候会去解析生成相关的bean,查看拥有相关注解的类和方法,并且为这些类和方法生成代理,根据@Transaction的相关参数进行相关配置注入,这样就在代理中把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。

3.真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

Spring 事务支持两种使用方式,分别是:声明式事务(注解方式)、编程式事务(代码方式)

1.在方法A 和方法B 上都加@Transactional :AB方法都会回滚,即插入失败

2.当methodA 不开启事务,methodB 开启事务(方法A不加@Transactional ,方法B加@Transactional ),这时候 methodB 就是独立的事务,而 methodA 并不在事务之中。因此当 methodB 发生异常回滚时,methodA 中的内容就不会被回滚。

3.当methodA 开启事务(方法A@Transactional ),methodB 开启事务(方法B加@Transactional(propagation = Propagation.REQUIRES_NEW) )时。

如果此时方法A发生异常,tablea 没有插入数据,tableb 插入了数据,即:父方法事务回滚了,但子方法事务没回滚。这可以证明父子方法的事务是独立的,不相互影响。

如果此时方法B发生异常,方法A又没有加try()catch()时,AB都会回滚。即子方法事务回滚,导致父方法事务也回滚了。

如果此时方法B发生异常,方法A加上try()catch()时,B方法回滚,A方法插入数据成功,达到父子事务是独立的,并不会相互影响。  父方法需要注意子方法抛出的异常,避免因子方法抛出异常,而导致父方法回滚。因为如果执行过程中发生 RuntimeException 异常和 Error 的话,那么 Spring 事务是会自动回滚的。

4.当methodA 开启事务(方法A@Transactional ),methodB 开启事务(方法B加@Transactional(propagation = Propagation.NESTED) )时。

如果此时方法A发生异常,tablea 没有插入数据,tableb 也没有插入数据。即父子方法事务都回滚了。这说明父方法发送异常时,子方法事务会回滚。

如果当子方法事务发生异常时,如果父方法没有捕捉异常,tablea 和 tableb 都没有插入数据,即:父子方法事务都回滚了。这说明子方法发送异常回滚时,如果父方法没有捕捉异常,那么父方法事务也会回滚。

如果当子方法事务发生异常时,如果父方法捕捉了异常,tablea 插入了数据,tableb 没有插入数据,即:父方法事务没有回滚,子方法事务回滚了。这说明子方法发送异常回滚时,如果父方法捕捉了异常,那么父方法事务就不会回滚。

常用函数:

Abs(num)求绝对值
floor(num)向下取整
ceil(num)向上取整
insert(s1,index,length,s2)替换函数:S1被替换的字符串,S2将要替换的字符串,index表示被替换的位置,从1开始,length表示被替换的长度。
upper(str)大写
lower(str)小写
left(str,length)返回str字符串的前length个字符
right(str,length)返回str字符串的后length个字符
substring(str,index,length)返回str字符串从index位开始长度为length个字符
reverse(str)将字符串倒序输出

datediff(d1,d2)d1和d2之间的天数差
adddate(date,num)返回date日期开始,之后num天的日期
subdate(date,num)返回date日期开始,之前num天的日期

代码优化1.索引
2.代码重构
3.增加缓存
4.引入一些中间件,比如mq
5.分库分表
6.拆分服务
7.池化处理
8.异步化处理CompleteFuture
9.避免大事务【@Transactional】].
  少用@Transactional注解
  将查询select方法放到事务外
  事务中避免远程调用
  事务中避免一次性处理太多数据
  有些功能可以非事务执行
  有些功能可以异步处理
10.分页处理

14.mysql的按照索引有哪些索引按照数据结构来说主要包含B+树和Hash索引。MySQL 中索引的存储类型有两种:btree 和 hash ,具体和表的存储引擎相关:MYISAM 和 InnoDB 存储引擎只支持btree 索引; memory/heap 存储引擎可以支持hash 和btree 索引。

索引:

索引是一个单独的,存储在磁盘上的数据库结构,它们包含着对数据表里所有记录的引用指针。使用索引用于快速找出在某个或多个列中有一特定值的行,所有MySQL 列类型都可以被索引,对相关列使用索引是提高查询操作速度的最佳途径。

索引在计算机中的位置:一般来说,索引本身也很大,不可能全部存储在内存中,一般以文件形式存储在磁盘上索引检索需要磁盘I/O操作。判断一种数据结构作为索引的优劣主要是看在查询过程中的磁盘IO渐进复杂度,一个好的索引应该是尽量减少磁盘IO操作次数。

索引分类:

1.普通索引和唯一索引:普通索引是MySQL 中的基本索引类型,允许在定义索引的列中插入重复值和空值。


唯一索引,索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。主键索引是一种特殊的唯一索引,不允许有空值。

2.单列索引和组合索引:单列索引即一个索引只包含单个列,一个表可以有多个单列索引。

3.全文索引:

全文索引类型为fulltext ,在定义索引的列上支持值的全文查找,允许在这些索引列中插入重复值和空指。全文索引可以在char,varchar 和text类型的列上创建。

MySQL 中只有MyISAM 存储引擎支持全文索引。

4.空间索引:不为null,MySQL 中只有MyISAM 存储引擎支持全文索引。

聚簇索引与非聚簇索引相关:

聚集索引概念|聚簇索引概念

聚集索引,主键索引等。表数据按照索引的顺序来存储的,即索引项的顺序与表中记录的物理顺序一致。对于聚集索引,叶节点即存储了真实的数据行,不再有另外单独的数据页。 在一张表上最多只能创建一个聚集索引,因为真实数据的物理顺序只能有一种。

聚集索引的特点:

1.如果表设置了主键,则主键就是聚簇索引

2.如果表没有主键,则会默认第一个NOT NULL,且唯一(UNIQUE)的列作为聚簇索引

3.以上都没有,则会默认创建一个隐藏的row_id作为聚簇索引

InnoDB的聚簇索引的叶子节点存储的是行记录(其实是页结构,一个页包含多行数据),InnoDB必须要有至少一个聚簇索引。 由此可见,使用聚簇索引查询会很快,因为可以直接定位到行记录。

B+Tree是左小右大的顺序存储结构。B+Tree只有叶节点存放数据,其余节点用来索引,B+Tree每个索引节点都会有data域(节点只包含id索引列,而叶子节点包含索引列和数据,这种数据和索 引在一起存储的索引方式叫做聚簇索引,一张表只能有一个聚簇索引。假设没有定义主键,InnoDB会选 择一个唯一的非空索引代替,如果没有的话则会隐式定义一个主键作为聚簇索引。)。B+Tree的特点每层节点数非常多,层数很少,目的就是为了减少磁盘io次数。B+Tree所有的data域在叶子节点,将所有的叶子节点用指针串起来,这样遍历叶子节点就能获取全部数据。

非聚集索引

非聚簇索引,二级索引。表数据存储顺序与索引顺序无关。对于非聚集索引,叶节点包含索引字段值和指向数据页数据行的逻辑指针,其行数量与数据表行数据量一致。InnoDB的普通索引叶子节点存储的是主键(聚簇索引)的值,而MyISAM的普通索引存储的是记录指针。

非聚簇索引(二级索引)保存的是主 键id值,这一点和MyIsam保存的是数据地址是不同的。

总结:

1.聚集索引是一种稀疏索引,数据页上一级的索引页存储的是页指针,而不是行指针。

2.对于非聚集索引,则是密集索引,在数据页的上一级索引页它为每一个数据行存储一条索引记录。

3.InnoDB的聚簇索引的叶子节点存储的是行记录(其实是页结构,一个页包含多行数据),4.InnoDB必须要有至少一个聚簇索引。由此可见,使用聚簇索引查询会很快,因为可以直接定位到行记录。

3.InnoDB的普通索引叶子节点存储的是主键(聚簇索引)的值,而MyISAM的普通索引存储的是记录指针。

覆盖索引

和回表

覆盖索引指的是在一次查询中,如果一个索引包含或者说覆盖所有需要查询的字段的值,我们就称之为 覆盖索引,而不再需要回表查询。而要确定一个查询是否是覆盖索引,我们只需要explain sql语句看Extra的结果是否是“Using index”即可。

create table `user`(
`id` int NOT NULL AUTO_INCREMENT,
`name` VARCHAR(60),
`age` TINYINT(4),
PRIMARY KEY (`id`),
index idx_age(`age`)
)ENGINE=innodb 

id 是主键,所以是聚簇索引,其叶子节点存储的是对应行记录的数据age 是普通索引(二级索引),非聚簇索引,其叶子节点存储的是聚簇索引的的值

1.如果查询条件为主键(聚簇索引),则只需扫描一次B+树即可通过聚簇索引定位到要查找的行记录数据。

举例:select * from user where id = 1;

2.如果查询条件为普通索引(非聚簇索引),需要扫描两次B+树,第一次扫描通过普通索引定位到聚簇索引的值,然后第二次扫描通过聚簇索引的值定位到要查找的行记录数据。
举例:select * from user where age = 30;

回表查询:先通过普通索引的值定位到聚簇索引值,再通过聚簇索引的值定位到行记录数据,要通过扫描两次索引B+树,它的性能较扫描一次较低。

索引覆盖:只需在一棵索引树上就能获取SQL所需的所有列数据,无需回表,速度更快。索引上的内容也不过是将表上的某些字段以B+树的结构储存起来,如果我们要查询的字段刚好就是索引包括的字段,那就可以在扫描完索引后直接得到结果,不需要回表,这就是覆盖索引。
举例:select id,age from user where age = 10;

如何实现覆盖索引:常见的方法是:将被查询的字段,建立到联合索引里去(若查询有where条件,同时where条件字段也必须为索引字段)。为了实现索引覆盖,需要建组合索引idx_age_name(age,name)
create index idx_age_name on user(`age`,`name`);


举例:select id,age from user where age = 10; 

此时的Extra列的【Using Index】表示进行了聚簇索引


explain分析:因为age是普通索引,使用到了age索引,通过一次扫描B+树即可查询到相应的结果,这样就实现了覆盖索引。此时的Extra列的【Using Index】表示进行了聚簇索引

select id,age,name from user where age = 10;
explain分析:age是普通索引,但name列不在索引树上,所以通过age索引在查询到id和age的值后,需要进行回表再查询name的值。


此时的Extra列的NULL表示进行了回表查询

哪些场景适合使用索引覆盖来优化SQL:
1.全表count查询优化
2.explain select id,age,name from user order by age limit 100,2;

如果使用非聚簇索引,都会先定位到聚簇索引上,如果有需要,再定位到表的数据上,这个由聚簇索引定位到数据表上的查询过程就叫做回表,所以非聚簇索引会走2次索引

那么有没有哪种情况可以让非聚簇索引也只走1次索引呢?有,那就是使用覆盖索引

type是index,而之前的type是ref,说明查找效率有一定下降,但是也用到了索引。在Extra中同时出现了Using where和Using index,可以理解为MySQL在查询时知道这不符合最左匹配,但是因为查询的字段都在索引中,所以可以在相比整个表数据更小的索引文件中查找,查询速度也比全表查询更快

B+Tree

BTree 指的是Balance Tree,也就是平衡树,平衡树是一颗查找树,并且所有的叶子节点位于同一层。使用B-Tree 数据结构来存储数据,B-Tree 通常意味着所有的值都是按照顺序存储的,并且每一个叶子页到根的距离相同。

1.文件很大,不可能全部存储在内存中,所以要存在磁盘上(B+Tree是用来充当索引的,一般来说索引非常大,尤其是关系型数据库这种数据量特别大的数据库。为了减少内存的占用,索引也会被存储在磁盘上

2.索引的组织结构要尽量减少查找过程中磁盘i/o 的存取次数(B+Tree的特点就是每层节点数非常多,层数很少,目的就是为了减少磁盘io次数,当查询数据时,很快找到目标索引,然后读取数据。)

3.B+Tree只有叶节点存放数据,其余节点用来索引,B+Tree每个索引节点都会有data域。B+Tree更适合磁盘数据。

B+Tree是左小右大的顺序存储结构。B+Tree只有叶节点存放数据,其余节点用来索引(其他节点不存储数据),B+Tree每个索引节点都会有data域。B+Tree的特点每层节点数非常多,层数很少,目的就是为了减少磁盘io次数。B+Tree所有的data域在叶子节点,将所有的叶子节点用指针串起来,这样遍历叶子节点就能获取全部数据。

B+Tree是左小右大的顺序存储结构,节点只包含id索引列,而叶子节点包含索引列和数据,这种数据和索 引在一起存储的索引方式叫做聚簇索引,一张表只能有一个聚簇索引假设没有定义主键,InnoDB会选 择一个唯一的非空索引代替,如果没有的话则会隐式定义一个主键作为聚簇索引。

基于B Tree 和叶子节点顺序访问指针进行实现,它具有B Tree 的平衡性,并且通过顺序访问指针来提高区间查询的性能。在这里,一个节点中的key 从左到右递增排列。进行查找操作时,首先在根节点进行二分查找,找到一个key所在的指针,然后递归的在指针所指向的节点进行查找。直到查找到叶子节点,然后再叶子节点上进行二分查找,找出key 所对应的data。

插入删除操作会破坏平衡树的平衡性,因此再操作后,需要对树进行一个分裂,合并,旋转来维护平衡性。

与红黑树的比较,选择B+Tree:红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用B+Tree 作为索引结构,主要原因:

1.更少的查找次数

2,利用磁盘预读特性:为了减少磁盘I/O 操作,磁盘往往不是严格按需读取,而是每次都会预读。

B-Tree 索引访问快速的原因:B-Tree 索引能够加快访问数据的速度,因为是从索引的根节点开始进行搜索。根节点的槽中存放了指向子节点的指针,存储引擎根据这些指针向下层查找。通过比较节点页的值和要查找的值可以找到合适的指针进入下层子节点,这些指针实际上定义了子节点页中值的上限和下限。最终存储引擎要么找到对应的值,要么记录不存在。

B-Tree 索引使用场景:使用B-Tree 索引的查询索引。B-Tree 索引适用于全键值,键值范围或键前缀查找,其中键前缀查找只适用于根据最左前缀的查找。

示例:create table tableName(
 last_name varchar(50) not null,
 first_name varchar(50) not null,
 key(last_name,first_name)
)

hash索引

Hash索引:哈希索引基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,哈希码是一个较小的值,并且不同键值的行计算出来的哈希码也不一样。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每个数据行的指针。只有Memory引擎显式的支持hash 索引,也是Memory默认的索引。

查询快速原因:因为索引自身只需存储对应的哈希值,所以索引的结构十分紧凑,这也让哈希表索引查找的速度非常快。

哈希索引的限制:

1.哈希索引只包含哈希值和行指针,而不存储字段值,所以不能使用索引中的值来避免读取行。不过,访问内存中的行的速度很快,所以一般情况下对性能不明显。

2.哈希索引数据并不是按照索引值顺序存储的,所以也就无法用于排序。

3.哈希索引页不支持部分索引列匹配查找,因为哈希索引始终是使用索引列的全部内容来计算哈希值的。

4.哈希索引只支持等值比较查询,包括 = ,in(), <= =>。也不支持任何范围查询,例如:where price > 100;

5.访问哈希索引的数据非常快,除非有很多哈希冲突。当出现哈希冲突的时候,存储引擎必须遍历链表中所有的行指针,逐行比较,直到找到所有符合条件的 行。

6.如果哈希冲突很多的话 ,一些索引未付操作的代价也会很高。

示例:

create table tableName(
 fname varchar(50) not null,
 key using hash(fname)
)

索引对

数据影响:

向表中插入新数据行:如果一张表没有聚集索引,那么它被称为“堆集”(Heap)。这样的表中的数据行没有特定的顺序,所有的新行将被添加到表的末尾位置。而建立了聚簇索引的数据表则不同:最简单的情况下,插入操作根据索引找到对应的数据页,然后通过挪动已有的记录为新数据腾出空间,最后插入数据。如果数据页已满,则需要拆分数据页,调整索引指针(且如果表还有非聚集索引,还需要更新这些索引指向新的数据页)。而类似于自增列为聚集索引的,数据库系统可能并不拆分数据页,而只是简单的新添数据页。

表中删除数据行:对删除数据行来说:删除行将导致其下方的数据行向上移动以填充删除记录造成的空白。如果删除的行是该数据页中的最后一行,那么该数据页将被回收,相应的索引页中的记录将被删除。对于数据的删除操作,可能导致索引页中仅有一条记录,这时,该记录可能会被移至邻近的索引页中,原索引页将被回收,即所谓的“索引合并”。

设计规则

索引设计不合理或者缺少索引都会对数据库和应用程序的性能造成障碍。高效的索引对于获得良好的性能非常重要:

0.对于小表,使用索引对于性能不会有任何提高。由于数据较少,查询花费的时间可能比遍历索引的时间还要短,索引可能不会产生优化效果。

1.索引会占用数据库的空间。设计数据库的可用空间时要考虑索引所占用的空间

2.更新非常频繁的字段不适合索引,更新索引的开销会降低你所期望获得的性能。并且索引中的列尽可能的少。而对经常用于查询的字段应该建立索引,但要避免添加不必要的字段。

3.当查询要返回的数据很少时索引可以优化你的查询(25%)

4.在频繁进行排序或分组的列上建立索引,如果待排序的列有多个,可以常见组合索引。对多个字段进行索引时,称为复合索引。使用复合索引时,要把最可能选择的字段放前面,这样可以提高查询的性能。组合索引指在表的多个字段组合上创建的索引,只有在查询条件中使用了这些字段的左边字段时,索引才会被使用。使用组合索引时遵循最左前缀集合。

5.当你的索引列中有极多的不同的数据和空值时索引会使性能有极大的提高

6.当唯一性是某种数据本身的特征时,指定唯一索引。使用唯一索引需要能够却把波定义的列的数据完整性,以提高查询速度(通过创建唯一索引,可以保证数据库表中每一行数据的唯一性。)。

7.区分度太差的字段不适合创建索引,尽管频繁作为查询条件,例如gender 性别字段。

8.不会出现在where 子句中的字段不该创建索引。

9.可以大大加快数据的查询速度,这也是创建索引的最主要的原因。

10.在使用分组和排序子句进行查询时,也可以显著减少查询中分组和排序的时间。对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引

11.建议单表的索引个数不要超过:5个。如果在建表时,发现索引个数超过5个了,可以删除部分普通索引,改成联合索引。

12.区分度最高的列放在联合索引的最左侧—>字段长度小的列放在联合索引的最左侧—>使用最频繁的列放在联合索引的最左侧(左前缀原则,原因是多个列排列创建联合索引,索引底层采用B+树数据结构,假设在查询时,where条件中左侧第一个列没有使用索引,或者使用了联合索引中非最左侧的列,通过该索引key找不到子节点,所以会走全表扫描)

索引的缺点:

0.删除索引以优化数据的载入

1.索引并非越多越好,一个表中如有大量的索引,不仅占用磁盘空间,而且也会影响insert,delete,update 等语句的性能,因为当表中的数据更改的同时,索引也会进行调整和更新。

2.索引可以提高数据的返回速度,但是它使得数据的更新操作变慢,在对大量记录进行更新时,应该先删除索引,当执行更新操作后,恢复索引。创建索引和维护索引要耗费时间,并且随着数据量的增加所耗费的时间也会增加。

3.索引需要占磁盘空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果有大量的索引,索引文件可能比数据文件更快达到最大文件尺寸。

4.当对表中中的数据进行增加,删除和修改的时候,索引也要动态的维护,这样这降低了数据的维护速度。

4.对字段的索引已经对两个表进行了归并操作,这一技术可以极大的提高归并的速度

不要将索引与表存储在同一个驱动器上,分开存储会去掉访问的冲突从而使结果返回得更快

索引失效:

0.减少不必要的表连接,有些数据操作的业务逻辑可以放到应用层进行实现

1.避免使用or,or 会不可避免的根据表的大小降低查询速度,尽量避免使用or语句,可以根据情况尽量使用union all或者in来代替,in通常会比or要快。用 exists 代替 in

2.where语句中索引列使用了负向查询,可能会导致索引失效,因为引擎放弃使用索引而进行全表扫描:NOT、!=、<>、!、not in、not like等。

3.使用is null或is not null时,索引字段为null,可能会导致索引失效

4.查询条件减少使用函数,避免全表扫描。在索引列上使用内置函数,一定会导致索引失效

5.对索引列进行运算,一定会导致索引失效

6.like通配符可能会导致索引失效

7.联合索引中,where中索引列违背最左匹配原则,一定会导致索引失效

8.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引

9.范围索引列没有放最后

10.使用了select * from 

11.字符类型没加引号

1.避免使用select *
2.用union all代替union
3.小表驱动大表
4.批量操作
5.多用limit
6.in中值太多
7.增量查询
8.高效的分页
9.用连接查询代替子查询
10.join的表不宜过多
11.join时要注意
12.控制索引的数量
13.选择合理的字段类型
14.提升group by的效率
15.索引优化
创建表的注意事项:

创建表

注意事项

1 见名知意:言简意赅:user_name

2 统一小写:因为直观:name

3 多个单词用下划线分隔符:product_name

4 表名,带上业务前缀:order_pay、order_pay_detail1.

5 相同意义的字段取相同的字段名称:create_time,id

6.字段类型:    尽可能选择占用存储空间小的字段类型,在满足正常业务需求的情况下,从小到大,往上选。    

1.如果字符串长度固定,或者差别不大,可以选择char类型。

2.如果字符串长度差别较大,可以选择varchar类型。

3.是否字段,选择bit类型。

4.枚举字段,选择tinyint类型。    

5.主键字段,可以选择bigint类型。  

6.金额字段选择decimal类型。

7.时间字段选择timestamp或datetime类型。

1.7字段个数:建议每表的字段个数,不要超过20个。如果真有这种情况,可以将一张大表拆成多张小表,这几张表的主键相同。表中保存的数据非常大,查询效率很低。  

8.在创建表时,一定要创建主键:因为主键自带了主键索引,相比于其他索引,主键索引的查询效率最高,因为它不需要回表。

   8-1.主键还是天然的唯一索引,可以根据它来判重。

   8-2.在单个数据库中,主键可以通过AUTO_INCREMENT,设置成自动增长的。

   8-3.在分布式数据库中,特别是做了分库分表的业务库中,主键最好由外部算法(比如:雪花算法)生成,它能够保证生成的id是全局唯一的。

建议:主键建议保存跟业务无关的值,减少业务耦合性,方便今后的扩展。

9.字段选择NOT NULL:

   9-1.在innodb中,需要额外的空间存储null值,需要占用更多的空间。

   9-2.null值可能会导致索引失效。

   9-3.null值只能用is null或者is not null判断,用=号判断永远返回false。

10.根据业务需求,在合适的列上创建索引:

   10-1.创建索引之后查表的时候,效率更高。

   10-2.索引字段不能建的太多,会影响保存数据的效率,因为索引需要额外的存储空间。

   10-3.建议单表的索引个数不要超过:5个。在建表时发现索引个数超过5个了,可以删除部分普通索引,改成联合索引。

   10-4.在创建联合索引的时候,需要使用注意最左匹配原则,不然,建的联合索引效率可能不高。

11.唯一索引:单个的唯一索引还好,但如果是联合的唯一索引,相关字段值一定不能出现null值,否则唯一性约束会失效。

12.金额字段:float和double可能会丢失精度,使用decimal类型保存金额。

举例定义浮点数的:decimal(m,n)。其中n是指小数的长度,而m是指整数加小数的总长度。假如我们定义的金额类型是这样的:decimal(10,2),则表示整数长度是8位,并且保留2位小数。

13.时间字段:可以选择datetime和timestamp类型保存日期和时间。它们的区别:

timestamp:用4个字节来保存数据,它的取值范围为1970-01-01 00:00:01 UTC ~ 2038-01-19 03:14:07。此外,它还跟时区有关。

datetime:用8个字节来保存数据,它的取值范围为1000-01-01 00:00:00 ~ 9999-12-31 23:59:59。它跟时区无关。

优先推荐使用datetime类型保存日期和时间,可以保存的时间范围更大一些。温馨提醒一下,在给时间字段设置默认值是,建议不要设置成:0000-00-00 00:00:00,不然查询表时可能会因为转换不了,而直接报错。

14.字符集:常用的有:latin1、utf-8、utf8mb4、GBK等:

   14-1.latin1容易出现乱码问题,在实际项目中使用比较少。

    14-2.GBK支持中文,但不支持国际通用字符,在实际项目中使用也不多。

    14-3.mysql的字符集使用最多的还是:utf-8和utf8mb4。

     14-4.utf-8占用3个字节,比utf8mb4的4个字节,占用更小的存储空间。

     14-5.utf-8有个问题:即无法存储emoji表情,因为emoji表情一般需要4个字节。由此,使用utf-8字符集,保存emoji表情时,数据库会直接报错。

     14-6.建议在建表时字符集设置成:utf8mb4,会省去很多不必要的麻烦。

15.排序规则:通过collate参数可以设置排序规则。字符排序规则跟字符集有关,比如:字符集如果是utf8mb4,则字符排序规则也是以:utf8mb4_开头的,常用的有:utf8mb4_general_ci、utf8mb4_bin等。

   

    15-1.utf8mb4_general_ci排序规则,对字母的大小写不敏感。即不区分大小写。

    15-2.utf8mb4_bin排序规则,对字符大小写敏感,也就是区分大小写。

     举例:select * from order where name='yoyo';

     15-3.如果字符排序规则是utf8mb4_general_ci,则可以查出大写的YOYO的那条数据。如果字符排序规则是utf8mb4_bin,则查不出来。

16.禁止外键:

   16-1.外键存在的主要作用是:保证数据的一致性和完整性。

   16-2.只有存储引擎是innodb时,才能使用外键。

   16-3.互联网系统中,一般建议不使用外键。因为这类系统更多的是为了性能考虑,宁可牺牲一点数据一致性和完整性。

17.选择数据类型:char 会删除尾部的空格且char 是固定长度字符;varchar 不会删除空格且varchar 是可变长度字符;char 处理速度比varchar 快,但会浪费存储空间。

18.字段长度:在mysql中除了varchar和char是代表字符长度之外,其余的类型都是代表字节长度。bigint(4),bigint实际长度是8个字节。

19.存储引擎:myslam的索引和数据分开存储,而有利于查询,但它不支持事务和外键等功能。而innodb虽说查询性能,稍微弱一点,但它支持事务和外键等,功能更强大一些。以前的建议是:读多写少的表,用myslam存储引擎。而写多读多的表,用innodb。现在myslam和innodb查询性能相差已经越来越小。

20.大字段:占用较多存储空间的字段。如果直接定义成text类型,可能会浪费存储空间,所以建议将这类字段定义成varchar类型的存储效率更高。一个合同可能会占几Mb。像合同这种非常大的数据,可以保存到mongodb中,然后在mysql的业务表中,保存mongodb表的id。

21. 索引名:ix_product_status,ux_product_status

存储引擎对于选择char 和varchar 的影响:
影响:

1.对于MyISAM存储引擎:最好使用固定长度的数据列代替可变长度的数据列。这样可以使整个表静态化,从而使数据检索更快,用空间换时间。

2.对于InnoDB 存储引擎:使用可变长度的数据列,因为InnoDB数据表的存储格式不分固定长度和可变长度,因此使用char 不一定比使用varchar 更好,但由于varchar 是按照实际的长度存储,比较节省空间,所以对磁盘I/O 和数据存储总量比较好。

MYSQL

日志分类

1.错误日志:记录MySQL 服务的启动,运行或停止MySQL 服务时出现的问题。

2.查询日志:记录建立的客户端连接和执行的语句。

3.二进制日志:记录所有更改数据的语句,可以用于数据复制。

4.慢查询日志:记录所有执行时间超过long_query_time 的所有查询或不使用索引的查询。

BTree

B树搜索有可能会在非叶子节点结束,越靠近根节点的记录查找时间越短,其性能等价于在关键字全集内做一次二分查找,查询时间复杂度不固定,与 key 在树中的位置有关,最好情况为O(1);

B树有如下特点:

0.根节点至少有两个子节点

1、每个节点都存储key和data,所有节点组成这棵树,并且叶子节点指针为null;

2、任何一个关键字出现且只出现在一个结点中;

3、搜索有可能在非叶子结点结束(最好情况是O(1)就能找到数据);

4、在关键字全集内做一次查找,性能逼近二分查找。

5.每个节点有M-1个key,并且以升序排列

6.其它节点至少有M/2个子节点

7.每个节点都存储key和data,所有节点组成这棵树,并且叶子节点指针为null。

8.位于M-1和M key的子节点的值位于M-1 和M key对应的Value之间

B+Tree

B+树有如下特点:

1、只有叶子节点存储data,包含了这棵树的所有键值,叶子节点不存储指针。(非叶子节点都只是存储索引值,没有实际的数据,并非真正的data);

2、增加了顺序访问指针,也就是每个叶子节点增加一个指向相邻叶子节点的指针,这样一棵树成了数据库系统实现索引的首选数据结构---B+树。

2.B+树空间利用率更高

3.因为B+树的内部节点(非叶子节点,也称索引节点)不存储数据,只存储索引值,相比较B树来说,B+树一个节点可存储更多的索引值,使得整颗B+树变得更矮,减少I/O次数,磁盘读写代价更低,I/O读写次数是影响索引检索效率的最大因素;
4、B+树查询效率更加稳定
 

5.因为在B+树中,顺序检索比较明显,随机检索时,由于B+树所有的 data 域(结点中存储数据元素的部分)都在根节点,任何关键字的查找都必须走一条从根节点到叶节点的路,所有关键字的查找路径相同,导致每一个关键字的查询效率基本相同,时间复杂度固定为 O(log n);而B树搜索有可能会在非叶子节点结束,约靠近根节点的记录查找时间越短,其性能等价于在关键字全集内做一次二分查找,查询时间复杂度不固定,与 key 在树中的位置有关,最好情况为O(1);
6、B+树范围查询性能更优

7.因为B+树的叶子节点使用了指针顺序,即链表结构,从小到大地连接在一起,B+树叶节点两两相连可大大增加区间访问性,只要遍历叶子节点就可以实现整棵树的遍历,而B树的叶子节点是相互独立的,每个节点 key(索引)和 data 在一起,则无法查找区间;

【根据空间局部性原理:如果一个存储器的某个位置被访问,那么将它附近的位置也会被访问】

若访问节点 key为 50,则 key 为 55、60、62 的节点将来也可能被访问,可利用磁盘预读原理提前将这些数据读入内存,减少了磁盘 IO 的次数。当然B+树也能够很好的完成范围查询,比如同时也会查询 key 值在 50-70 之间的节点。

8.B+树增删文件(节点)时,效率更高.因为B+树的叶子节点包含了所有关键字,并以有序的链表结构存储。
 

说明:InnoDB的一棵B+树可以存放多少行数据?约2千万。

B+树【B树的改进版, 让内部节点(非叶子节点)只作索引使用,叶子节点包含了这棵树的所有键值,叶子节点不存储指针】

B+ 树的优点在于:
由于B+树在内部节点上不包含数据信息,因此在内存页中能够存放更多的key。 数据存放的更加紧密,具有更好的空间局部性。因此访问叶子节点上关联的数据也具有更好的缓存命中率。
B+树的叶子结点都是相连的,因此对整棵树的便利只需要一次线性遍历叶子结点即可。而且由于数据顺序排列并且相连,所以便于区间查找和搜索。
而B树则需要进行每一层的递归遍历。相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

但是B树也有优点,其优点在于,由于B树的每一个节点都包含key和value,因此经常访问的元素可能离根节点更近,因此访问也更迅速


B+树的特点:
只有叶子节点存储data,叶子节点包含了这棵树的所有键值,所有叶结点构成一个有序链表,非叶子节点只存储导航信息。

B+树是对B树的一种变形树,它与B树的差异在于:

有k个子结点的结点必然有k个关键码;
非叶结点仅具有索引作用,跟记录有关的信息均存放在叶结点中。
树的所有叶结点构成一个有序链表,可以按照关键码排序的次序遍历全部记录。

BTree和B+Tree的区别:

1、BTree的关键字和记录是放在一起的,叶子节点可以看作外部节点,不包含任何信息;

     B+Tree的非叶子节点中只有关键字和指向下一个节点的索引,记录只放在叶子节点中。

2、在BTree中,越靠近根节点的记录查找时间越快,只要找到关键字即可确定记录的存在;

      B+Tree中每个记录的查找时间基本是一样的,都需要从根节点走到叶子节点,而且在叶子节点中还要再比较关键字。

3.单行数据:对于k-v单行数据查询来说,B树和B+树其实都可以,假如我们要查询0005这个数据,B树反尔比B+树查询出结果来的要快。

4.范围查询:区别比较大,对于B+Tree来说,只需要通过索引找到范围查询的左边界,然后通过链表遍历即可。而对于B树来说,对于每个数据来说都需要进行反复的索引判断,效率远不如B+树的遍历来的快。

5.查询顺序:B+Tree同时支持随机检索和顺序检索。BTree只适合随机检索。

6.B+树的data只存储在叶子节点上,B树的所有节点都存储了key和data。B+Tree的非叶节点不存储data,这样一个节点就可以存储更多的索引值,可以使得树更矮(高度更小),所以IO操作次数更少。

7.B+Tree的所有叶结点构成一个有序链表,可以按照关键码排序的次序来有序遍历全部记录
由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历,相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。

对比项MyISAMInnoDB
主键、外键不支持支持
事务不支持支持
行表锁表锁,即使操作一条记录也会锁住整个表,不适合高并发的操作。行锁,操作时,只锁住某一行,不对其他行有影响,适合高并发操作。
缓存只缓存索引,不缓存真实数据。不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响。
表空间小 
关注点性能事务
默认安装YY

13.MyIsam 和 Innodb的区别:

MyIsam

MyIsam引擎是5.1版本之前的默认引擎,支持全文检索、压缩、空间函数等,但是不支持事务和行级 锁,所以一般用于有大量查询少量插入的场景来使用,而且MyIsam不支持外键,并且索引和数据是分开存储的。

MyISAM拥有较高的插入,查询速度,但不支持事务。

1.大文件在支持大文件的文件系统和操作系统上被支持。

2.当把删除和更新及插入操作混合使用的时候,动态尺寸的行产生更少碎片。这要通过合并相邻被删除的块,以及若下一个块被删除,就扩展到下一块来自动完成。

3.每一个MyISAM 表最大索引数是64,这可以通过从新编译来改变。每个索引最大的列数是16个。

4.最大的键长度是1000字节,这也可以通过编译来改变。对于键长度超过250字节的情况,一个超过1024字节的键将被用上。

5.BOLB和TEXT列可以被索引。

6.Null值被允许在索引的列中。这个值占每个键的0-1个字节。

7.所有数字键值以高字节优先被存储以允许一个更高的索引压缩。

总结:如果数据表主要用来插入和查询记录,则MyISAM引擎能提供较高的处理效率;

Innodb

Innodb是基于聚簇索引建立的,支持事务、外键,并且通过MVCC来支持高并发,索 引和数据存储在一起。

Innodb 事务型数据库的首选引擎,支持事务安全表ACID,支持行锁定和外键。

1.Innodb给MySQL提供了具有提交,回滚和崩溃恢复能力的事务安全存储引擎。Innodb锁定在行级并且也在select语句中提供一个类似Oracle的非锁定读。这些功能增加了多用户部署和性能。在SQL查询中,可以自由的将InnoDB类型的表与其他MYSQL的表的类型混合起来,甚至在同一个查询中也可以混合。

2.InnoDB是为处理巨大数据量的最大性能设计。它的CPU效率可能是任何其他基于磁盘的关系数据库引擎所不能匹敌的。

3.InnoDB存储引擎完全与Mysql 服务器整合,InnoDB存储引擎为在主内存中缓存数据和索引而维持它自己的缓冲池。InnoDB将它的表和索引在一个逻辑表空间中,表空间可以包含数个文件。这与MYISAM不同。

InnoDB 支持外键完整性约束:存储表中的数据时,每张表的存储都按主键顺序存放,如果没有显示在表定义时定义时指定主键,InnoDB 会为每一行生成一个6字节的ROWID ,并以此作为主键。

总结:如果要提供提交,回滚和崩溃恢复能力的事务安全(ACID)能力,并要求实现并发控制,InnoDB是个很好的选择。

Memory

如果只是临时存放数据,数据量不大,并且不需要较高的数据安全性,可以选择将数据保存内存中的Memory引擎,MySQL中使用该引擎作为临时表,存放查询的中间结果。

总结:Memory存储引擎将表中的数据存储到内存中,为查询和引用其他表数据提供快速访问。

Archive

如果只有Insert,select 操作,可以选择Archive 引擎,Archive 存储引擎支持高并发的插入操作,但是本身并不适合事务安全的。Archive 存储引擎非常适合存储归档数据,如记录日志信息可以使用Archive 引擎。

Archive 引擎会缓存所有的写并利用Zlib 对插入的行进行压缩,所以比MyISAM 表的磁盘I/O 更少。但是每次select 查询都需要执行全表扫面。

各种存储引擎的比较:

功能MyISAMMemoryInnoDBArchive
存储限制256TBRAM64TBNone
支持事务nono yesno
支持全文索引yesnonono
支持数索引yesyesyesno
支持哈希索引noyesnono
支持数据缓存noN/Ayesno
支持外键nonoyesno

17.锁的类型有哪些:mysql锁分为共享锁和排他锁,也叫做读锁和写锁。

读锁

共享锁

读锁是共享的,可以通过lock in share mode实现,此时只能读不能写。 

加了共享锁的数据对象可以被其他事务读取,但不能修改。

读锁是共享的,相互不阻塞。多个客户在同一时刻可以同时读取同一个资源,而互不干扰

写锁

排它锁

写锁是排他的,它会阻塞其他的写锁和读锁从颗粒度来区分,可以分为表锁和行锁两种

当数据对象被加上排它锁时,其他的事务不能对它读取和修改。

写锁是排他的,一个写锁会阻塞其他的写锁和读锁,出于安全策略,只有这样,才能保证在给定的时间里,只有一个用户能执行写入,并防止其他用户读取正在写入的同一资源。

总结:

在数据库中有两种基本的锁类型:排它锁(Exclusive Locks,即X锁)和共享锁(Share Locks,即S锁)。数据库利用这两种基本的锁类型来对数据库的事务进行并发控制。innodb中,还用undolog实现多版本并发控制。

锁粒度:一种提高共享资源并发性的方式就是让锁定对象更有选择性,尽量只锁定需要修改的部分数据,而不是全部资源。或者只对修改的数据片进行精确的锁定,锁定的数据量越少,系统的并发程度越高。

存在问题:加锁需要消耗资源。锁的各种操作,包括获得锁,检查锁是否已经解除,释放锁等,都会增加系统的开销。需要在锁的开销和数据的安全性之间寻求平衡[一般都是在表上施加行级锁比较好]。

表锁table lock

表锁会锁定整张表并且阻塞其他用户对该表的所有读写操作,比如alter修改表结构的时候会锁表。

开销小,加锁快,不会出现死锁。锁定力度大,发生粒度大,发生锁冲突的概率最高,并发量最低。

表锁是mysql 中最基本的锁策略,并且是开销最小的策略。它会锁定整张表,一个用户在对表进行写操作前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他读取的用户才能获得读锁,读锁之间是不相互阻塞的。


在特定的场景中,表锁也可能有良好的性能,read,local 表锁支持某些类型的并发写操作。写锁比读锁有更高的优先级,所以一个写锁的请求可能会被插入到读锁队列的前面。

注解:服务器会为alter table 之类的语句使用表锁。

行锁row lock 

行锁又可以分为乐观锁和悲观锁,悲观锁可以通过for update实现,乐观锁则通过版本号实现。

开销大,加锁慢,会出现死锁。锁力度小,发生锁冲突的概率小,并发度最高。

单条索引记录上加锁,锁住的永远是索引,而非记录本身。

行级锁可以最大程度的支持并发处理(最大的锁开销)。InnoDB 和 XtraDB ,以及其他一些存储引擎中实现了行级锁。行级锁只在存储引擎层实现,MySQL 服务层没有实现。

Record locks锁定一个记录上的索引,而不是记录本身。如果表没有设置索引,InnoDB 会自动的在主键上创建隐藏的聚簇索引,因此Record locks 依然可以使用。
间隙锁Gap lock。在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。
临键锁Next-key lock,Record lock 和 Gap lock 的结合,即除了锁住记录本身,也锁住索引之间的间隙。锁定索引之间的间隙,但是不包括索引本身。
总结:Mylsam 在5.1之前是默认的存储引擎,它提供了大量的特性,包括全文索引,压缩,空间函数等,但不支持事务和行级锁。还有就是崩溃后无法安全回复。它是表级锁。
悲观锁概念:

悲观锁指的是数据被外界修改持有保守态度,认为数据很容易就被其他线程修改,所以在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态

select name from user where id = #{id} for update;

id字段一定是主键或者唯一索引,不然是锁表。

缺点:

使用悲观锁进行控制。悲观锁大多数情况下依靠数据库的锁机制实现,如Oracle的Select … for update语句,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。

乐观锁:

乐观锁认为数据在一般情况下不会造成冲突,所以在访问记录前不会加排他锁,而是在进行数据更新时,才会正式对数据冲突与否进行检测。乐观锁大多是基于数据版本(Version)记录机制实现。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于>数据库表当前版本号,则予以更新,否则认为是过期数据。

selct name,version from user where id = #{id};
update  user set name = #{name} ,version = #{version}+1 where id = #{id} and version = #{version};

优点:乐观锁机制避免了长事务中的数据库加锁开销,大大提升了大并发量下的系统整体性能表现。Hibernate 在其数据访问引擎中内置了乐观锁实现。需要注意的是,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。

总结:

MyIsam支持表锁,InnoDB支持表锁和行锁,默认是行锁。

乐观锁并不会使用数据库提供的锁机制,一般在表中添加version 字段或者使用业务状态来实现。乐观锁直到提交时才锁定,所以不会产生死锁。

举例:

-- id 列为主键列或唯一索引列 #这时候 id 为 1 的记录行会被锁住。
select * from tableName where  id = 1 for update;

总结:

id 列必须为唯一索引列或主键列,否则上述语句加的锁就会变成临键锁(行锁在 InnoDB 中是基于索引实现的,所以一旦某个加锁操作没有使用索引,那么该锁就会退化为表锁)同时查询语句必须为精准匹配=,不能为>、<、like等,否则也会退化成临键锁。

1.使用行锁要基于索引实现,若没有使用索引则会自动变为表锁。

2.查询语句必须是精准匹配,若查询为模糊查询或范围查询,该锁会退化为临键锁。

3.行锁必须使用唯一索引或主键索引,若不满足则会变为临键锁。

在通过主键索引与唯一索引对数据行进行update操作时,也会对该行数据加记录锁:


-- id 列为主键列或唯一索引列
UPDATE SET age = 50 WHERE id = 1;

间隙锁基于非唯一索引,它锁定一段范围内的索引记录。间隙锁基于下面将会提到的Next-Key Locking 算法,使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。

select * from tableName where id between 1 and 10 for update;

#for update:在mysql中表示加:排他锁(X锁, exclusive locks),即所有在(1,10)区间内的记录行都会被锁住,所有id 为 2、3、4、5、6、7、8、9 的数据行的插入会被阻塞,但是 1 和 10 两条记录行并不会被锁住。


除了手动加锁外,在执行完某些 SQL 后,InnoDB 也会自动加间隙锁。

Next-Key 可以理解为一种特殊的间隙锁,也可以理解为一种特殊的算法。通过临建锁可以解决幻读的问题。 每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。InnoDB 中的行锁的实现依赖于索引,一旦某个加锁操作没有使用到索引,那么该锁就会退化为表锁。记录锁存在于包括主键索引在内的唯一索引中,锁定单条索引记录。
 

间隙锁存在于非唯一索引中,锁定开区间范围内的一段间隔,它是基于临键锁实现的。在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。临键锁存在于非唯一索引中,该类型的每条记录的索引上都存在这种锁,它是一种特殊的间隙锁,锁定一段左开右闭的索引区间。即除了锁住记录本身,也锁住索引之间的间隙。

InnoDB 行锁是通过索引上的索引项来实现的。意味者:只有通过索引条件检索数据,InnoDB 才会使用行级锁,否则,InnoDB将使用表锁!

对于主键索引:直接锁住锁住主键索引即可。
对于普通索引:先锁住普通索引,接着锁住主键索引,这是因为一张表的索引可能存在多个,通过主键索引才能确保锁是唯一的,不然如果同时有2个事务对同1条数据的不同索引分别加锁,那就可能存在2个事务同时操作一条数据了。

MySQL如何解决不可重复读:
MySQL中,默认使用的事务隔离界别是可重复读,为了解决不可重复读问题,InnoDB采用了MVCC(多版本并发控制)【基于乐观锁】来解决!
MVCC(多版本并发控制)是利用在每条数据后面加了隐藏的两列(创建版本号和删除版本号),每个事务在开始的时候都会有一个递增的当前事务版本号!

查询操作为了避免查询到旧数据或已经被其他事务更改过的数据,需要满足如下条件:

1、查询时当前事务的版本号需要大于或等于创建版本号create_version

2、查询时当前事务的版本号需要小于删除的版本号delete_version,或者当前删除版本号delete_version=NULL

即:(create_version <= current_version < delete_version) || (create_version <= current_version && delete_version-=NULL),这样就可以避免查询到其他事务修改的数据,同一个事务中,实现了可重复读!

幻读:在一个事务中使用相同的 SQL 两次读取,第二次读取到了其他事务新插入的行,则称为发生了幻读。

例如:

1)事务1第一次查询:select * from user where id < 10 时查到了 id = 1 的数据
2)事务2插入了id = 2 的数据
3)事务1使用同样的语句第二次查询时,查到了 id = 1、id = 2 的数据,出现了幻读。

谈到幻读,首先我们要引入“当前读”和“快照读”的概念,通过名字就可以理解:

快照读:生成一个事务快照(ReadView),之后都从这个快照获取数据。普通 select 语句就是快照读。
 

当前读:读取数据的最新版本。常见的 update/insert/delete、还有 select ... for update、select ... lock in share mode 都是当前读。

对于快照读,MVCC 因为因为从 ReadView 读取,所以必然不会看到新插入的行,所以天然就解决了幻读的问题。

而对于当前读的幻读,MVCC 是无法解决的。需要使用 Gap Lock 或 Next-Key Lock(Gap Lock + Record Lock)来解决。

其实原理也很简单,用上面的例子稍微修改下以触发当前读:select * from user where id < 10 for update,当使用了 Gap Lock 时,Gap 锁会锁住 id < 10 的整个范围,因此其他事务无法插入 id < 10 的数据,从而防止了幻读。

MVCC多版本并发控制

MVCC((Mutil-Version Concurrency Control)),多版本并发访问,一种并发环境下进行数据安全控制的方法,本质上是一种乐观锁,用于实现读已提交(READ COMMITTD)和可重复读(REPEATABLE READ)这两种隔离级别。常说的MVCC是由MySQL数据库InnoDB存储引擎实现的,并非是由MySQL本身实现的,不同的存储引擎,对MVCC都有不同的实现标准。

InnoDB使用MVCC来解决并发问题的方法,让每个不同的事务访问查询同一行数据时,每个事务修改的都是这行数据的不同版本,InnoDB只需要去记录这个数据的访问链,就可以实现一个SELECT操作的并发执行。MVCC利用了多版本的思想,在 MVCC 中事务的所有写操作(INSERT、UPDATE、DELETE)会为数据行新增一个最新的版本快照,而读操作是去读旧版本的快照,也就是说,读操作和写操作是分离的,二者之间没有依赖、互斥关系。innodb中,还用undolog实现多版本并发控制。Undo Log是MySQL的三大日志之一,当我们对记录做了变更操作时就会产生一条Undo记录。它的作用就是保护事务在异常发生的时候或手动回滚时可以回滚到历史版本数据,能够让你读取过去某一个时间点保存的数据。通俗易懂地说,它只关心过去的数据。

对于一个InnoDB存储引擎,一个聚簇索引(主键索引)的记录之中,一定会有两个隐藏字段trx_id和roll_pointer,这两个字段存储于B+树的叶子节点中,分别对应记录着两列信息:

trx_id:只要有任意一个事务对某条聚簇索引记录进行修改,该事务id就会被记录到该字段里面,trx_id就是记录修改了每条聚簇索引的事务id。

roll_pointer:当任意一个聚簇索引记录被修改,上一个版本的数据记录就会被写入Undo Log日志里面。那么这个roll_pointer就是存储了一个指针,这个指针是一个地址,指向这个聚簇索引的上一个版本的记录位置,通过这个指针就可以获得到每一个历史版本的记录。roll_pointer就是个指针,指向每一个历史操作版本的数据存储的地址;每一次修改操作都会生成一个Undo Log版本,每个版本之间是隔离的。如果接下去有事务C,事务D等等一直对这条记录进行修改,那么这条记录的roll_pointer指针就会一直这样递归修改下去,最终形成一个关于修改和删除操作的Undo Log版本链!

InnoDB只有两种版本链:insert undo log(插入操作产生) 和update undo log(更新操作产生)。而查询没有,因为查询操作不会生成Undo Log版本链。

Read View 存放着一个列表,这个列表用来记录当前数据库系统中活跃的读写事务,也就是已经开启了,正在进行数据操作但是还未提交保存的事务。可以通过这个列表来判断某一个版本是否对当前事务可见。其中,有四个重要的字段:

creator_trx_id:创建当前Read View所对应的事务ID

m_ids:所有当前未提交事务的事务ID,也就是活跃事务的事务id列表

min_trx_id:m_ids里最小的事务id值

max_trx_id:InnoDB 需要分配给下一个事务的事务ID值(事务 ID 是累计递增分配的,所以后面分配的事务ID一定会比前面的大!)

每一个事务去读取或者修改同一个记录时,只能操作已经提交了的数据,未提交的数据是不能读取到的。可重复,就这样实现了。

其实就是通过Read View的字段判断这行记录对自己是否可见,如果不可见的话再去找Undo Log里面记录的对自己可见的数据,然后操作就可以了。

InnoDB 中,MVCC 就是通过 Undo Log + Read View 进行数据读取,Undo Log 保存了历史快照,而 Read View 规则帮我们判断当前版本的数据是否可见。从而不需要通过加锁的方式,就可以实现提交读和可重复读这两种隔离级别。

总的来说,MVCC本质上就是一种数据结构。已提交读和可重复读都是使用了Read View这种策略通过区间判断获取自己能够读取的内容,然后展示。InnoDB通过MVCC,解决了脏读、不可重复读。

MySQL 的大多数事务性存储实现的都不是简单的行级锁,而是实现了多版本并发控制MVCC,非阻塞的读操作,写也只是锁定该行而已。MVCC 只在read committed 和 repeatable read 两个隔离级别下工作。MVCC使用到的快照存储在Undo 日志中,该日志通过回滚指针把一个数据行record 的所有快照连接在一起。

InnoDB 是MySQL的默认事务性引擎,默认隔离级别是repeatable read 可重复度,并且通过间隙锁next-key locking 策略防止幻读的出现。InnoDB是基于聚簇索引建立的。

隐式和显式锁定:InnoDB 采用的是两阶段锁定协议(two-phase locking protocol)。在事务执行过程中,随时都可以执行锁定,锁只有在执行commit 或者rollback 的时候才会释放,并且所有的锁是在同一时刻被释放。

mvcc实现:

MVCC的实现

MVCC的实现,是通过保存数据在某个时间点的快照实现的,不管需要执行多久时间,每个事务看到的数据是一致的。根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。

InnoDB 的MVCC,是通过每行记录后面保存两个隐藏的列实现的。两列分别保存了创建时间和过期时间[系统版本号]。每开始一个新的事务,系统版本号都会递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询到的每行记录的版本号进行比较。

MVCC

MVCC 是行级锁的一个变种,但它在很多情况下避免了加锁操作,因此开销更低。实现机制不同,但都实现了非阻塞的读操作,写操作也只锁定必要的行。

MySQL 的大多数事务性存储实现的都不是简单的行级锁,而是实现了多版本并发控制MVCC,非阻塞的读操作,写也只是锁定该行而已MVCC 只在read committed 和 repeatable read 两个隔离级别下工作MVCC使用到的快照存储再Undo 日志中,该日志通过回滚指针把一个数据行record 的所有快照连接在一起。

提升性能MySQL 的大多数事务性存储实现的都不是简单的行级锁,基于提升并发性能考虑,而是实现了多版本并发控制MVCC。mysql,Oracle,postgresql 等其他数据库系统也都实现了MVCC,但各自的实现机制不尽相同。
注意:

MVCC 只在repeatable read 和 read committed两个隔离级别下工作。其他两个隔离级别都和MVCC 不兼容。因为read uncommitted 总是读取最新的数据行,而不是符合当前事务版本的数据行。而serializable 则会对所有读取的行都加锁。

并发控制:只要有多个查询需要在同一时刻修改数据,都会产生并发控制的问题。
mysql 在两个层面做并发控制:服务器层与存储引擎层。
读写锁:解决并发问题,可以通过实现一个由两种类型的锁组成的锁来解决问题,这两种类型的锁称为共享锁shared lock和排他锁 exclusive lock,也叫读锁 read lock和写锁 write lock。

25.数据库解决死锁的方法:

死锁:死锁是指两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。多个事务同时锁定同一个资源时,也会产生死锁。
总结:数据库系统实现了各种死锁检测和死锁超时机制。越复杂的系统,InnoDB存储引擎,越能检测到死锁 的循环依赖,并理解返回一个错误。
调整程序的顺序调整的程序的逻辑,仔细分析程序的逻辑,对于数据库的多表操作时,尽量按照相同的顺序进行处理,尽量避免同时锁定两个资源,如操作A和B两张表时,总是按先A后B的顺序处理,必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源。
按钮1、对于按钮等控件,点击后使其立刻失效,不让用户重复点击,避免对同时对同一条记录操作。
乐观锁2、使用乐观锁进行控制。乐观锁大多是基于数据版本(Version)记录机制实现。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。乐观锁机制避免了长事务中的数据库加锁开销(用户A和用户B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。Hibernate 在其数据访问引擎中内置了乐观锁实现。需要注意的是,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。
悲观锁3、使用悲观锁进行控制。悲观锁大多数情况下依靠数据库的锁机制实现,如Oracle的Select … for update语句,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户账户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对成百上千个并发,这样的情况将导致灾难性的后果。所以,采用悲观锁进行控制时一定要考虑清楚。
sql 简单化SQL语句中不要使用太复杂的关联多表的查询;使用“执行计划”对SQL语句进行分析,对于有全表扫描的SQL语句,建立相应的索引进行优化。
多版本并发控制:MySQL 的大多数事务性存储实现的都不是简单的行级锁,而是实现了多版本并发控制MVCC,非阻塞的读操作,写也只是锁定该行而已MVCC 只在read committed 和 repeatable read 两个隔离级别下工作MVCC使用到的快照存储再Undo 日志中,该日志通过回滚指针把一个数据行record 的所有快照连接在一起。
mysql 默认事务性引擎InnoDB 是MySQL的默认事务性引擎,默认隔离级别是repeatable read 可重复度,并且通过间隙锁next-key locking 策略防止幻读的出现。InnoDB是基于聚簇索引建立的。
Record locks: 锁定一个记录上的索引,而不是记录本身。如果表没有设置索引,InnoDB 会自动的在主键上创建隐藏的聚簇索引,因此Record locks 依然可以使用。

最左匹配原则:最左匹配原则指的就是如果你的 SQL 语句中用到了联合索引中的最左边的索引,那么这条 SQL 语句就可以利用这个联合索引去进行匹配,值得注意的是,当遇到范围查询(>、<、between、like)就会停止匹配。

假设,我们对(a,b)字段建立一个索引,也就是说,你where后条件为:

a = 1
a = 1 and b = 2
上面的两个查询条件都是可以走联合索引。

但一定要注意的是:

b= 2 and a =1
也是能匹配到索引的,因为Mysql有优化器会自动调整a,b的顺序与索引顺序一致。

但是如果执行的是:

b = 2
这个是一定不会走联合索引的。

对(a,b,c,d)建立索引,where后条件为:

a = 1 and b = 2 and c > 3 and d = 4
那么,a,b,c三个字段能用到索引,而d就匹配不到。因为遇到了范围查询!

2、最左匹配原理:在遇到范围查询的时候,就会停止匹配。

3、题目:

SELECT * FROM table WHERE a = 1 and b = 2 and c = 3

(a,b,c)或者(c,b,a)或者(b,a,c)都可以(也就是这一题对三个怎么建索引的顺序,是不重要的,因为不管怎么个顺序,优化区都会在执行的时候帮助我们调整顺序,这一题关注的不是顺序,而是要将哪些字段放在前面),重点要的是将区分度高的字段放在前面,区分度低的字段放后面。

例如假设区分度由大到小为b,a,c。那么我们就对(b,a,c)建立索引。在执行sql的时候,优化器会帮我们调整where后a,b,c的顺序,让我们用上索引。

SELECT * FROM table WHERE a > 1 and b = 2

对(b,a)建立索引。如果你建立的是(a,b)索引,那么只有a字段能用得上索引,毕竟最左匹配原则遇到范围查询就停止匹配。 如果对(b,a)建立索引那么两个字段都能用上,优化器会帮我们调整where后a,b的顺序,让我们用上索引。

SELECT * FROM `table` WHERE a > 1 and b = 2 and c > 3

(b,a)或者(b,c)都可以,要结合具体情况具体分析,如果a的区分度比较高就采用(b,a),否则就采用(b,c)。

SELECT * FROM `table` WHERE a = 1 ORDER BY b

对(a,b)建索引,当a = 1的时候,b相对有序,可以避免再次排序!

SELECT * FROM `table` WHERE a > 1 ORDER BY b

对(a)建立索引,因为a的值是一个范围,这个范围内b值是无序的,没有必要对(a,b)建立索引

SELECT * FROM `table` WHERE a IN (1,2,3) and b > 1

对(a,b)建立索引,因为IN在这里可以视为等值引用,不会中止索引匹配,所以还是(a,b)。

MySQL数据表在什么情况下容易损坏:

服务器突然断电导致数据文件损坏;强制关机,没有先关闭MySQL服务等。

MySQL如何优化distinct:

distinct在所有列上转换为group by,并与order by子句结合使用。

一张表里面有 ID 自增主键,当 insert 了 17 条记录之后,删除了第 15,16,17 条记录, 再把 Mysql 重启,再 insert 一条记录,这条记录的 ID 是 18 还是 15:

1.如果表的类型是 MyISAM那么是 18,因为 MyISAM 表会把自增主键的最大 ID 记录到数据文件里,重启 MySQL 自增主键的最大ID 也不会丢失.

2.如果表的类型是 InnoDB,那么是 15,InnoDB 表只是把自增主键的最大 ID 记录到内存中所以重启数据库或者是对表进行OPTIMIZE 操作,都会导致最大 ID 丢失

Heap 表是什么:

HEAP 表存在于内存中,用于临时高速存储。
BLOB 或 TEXT 字段是不允许的,只能使用比较运算符=,<,>,=>,= <


HEAP 表不支持 AUTO_INCREMENT,索引不可为 NULL

FLOAT 和 DOUBLE 的区别:

浮点数以 8位精度存储在 FLOAT 中,并且有四个字节
浮点数存储在 DOUBLE 中,精度为
18位,有八个字节

char_length 和length的区别:

1.char_length是字符数,而length 是字节数。

2.Latin 字符的这两个数据是相同的,但是对于 Unicode 和其他编码,它们是不同的。

3.CHAR 和 VARCHAR 类型在存储和检索方面有所不同

4.CHAR 列长度固定为创建表时声明的长度,长度值范围是 1 到 255
当 CHAR 值被存储时,它们被用空格填充到特定长度,检索 CHAR 值时需删除尾随空格。

TIMESTAMP 在 UPDATE CURRENT_TIMESTAMP 数据类型上做什么:


创建表时 TIMESTAMP 列用 Zero 更新。只要表中的其他字段发生更改,UPDATE
CURRENT_TIMESTAMP 修饰符就将时间戳字段更新为当前时间。

如果一个表有一列定义为 TIMESTAMP,将发生什么:


每当行被更改时,时间戳字段将获取当前时间戳。

主键和候选键有什么区别:


表格的每一行都由主键唯一标识,一个表只有一个主键。
主键也是候选键。按照惯例,候选键可以被指定为主键,并且可以用于任何外键引用。

myisamchk 是用来做什么的:


它用来压缩 MyISAM 表,这减少了磁盘或内存使用。

如何控制 HEAP 表的最大尺寸:


Heal 表的大小可通过称为 max_heap_table_size 的 Mysql 配置变量来控制。

MyISAM Static 和 MyISAM Dynamic 有什么区别:


在 MyISAM Static 上的所有字段有固定宽度。动态 MyISAM 表将具有像 TEXT,BLOB 等字
段,以适应不同长度的数据类型。点击这里有一套最全阿里面试题总结。
MyISAM Static 在受损情况下更容易恢复。

列设置为 AUTO INCREMENT 时,如果在表中达到最大值,会发生什么情况:


它会停止递增,任何进一步的插入都将产生错误,因为密钥已被使用。

找出最后一次插入时分配了哪个自动增量:


LAST_INSERT_ID 将返回由 Auto_increment 分配的最后一个值,并且不需要指定表名称。

blob 和 text 有什么区别:


blob是一个二进制对象,可以容纳可变数量的数据。

有四种类型的Blob:TinyBlob,Blob,MediumBlob,LongBlog,它们只能在所能容纳价值的最大长度上有所不同。


text 是一个不区分大小写的 BLOB。

四种text 类型:TinyText,text,MediumText 和,LongText。它们对应于四种 BLOB 类型,并具有相同的最大长度和存储要求。


BLOB 和 TEXT 类型之间的唯一区别在于对 BLOB 值进行排序和比较时区分大小写,对 TEXT
值不区分大小写。

Mysql 如何优化 DISTINCT:

DISTINCT 在所有列上转换为 GROUP BY,并与 ORDER BY 子句结合使用。

SELECT DISTINCT t1.a FROM t1,t2 where t1.a=t2.a;

如何显示前 50 行:

SELECT*FROM LIMIT 0,50;

可以使用多少列创建索引:


任何标准表最多可以创建 16 个索引列。

NOW()和 CURRENT_DATE()有什么区别:


NOW()命令用于显示当前年份,月份,日期,小时,分钟和秒。
CURRENT_DATE()仅显示当前年份,月份和日期。

MYSQL 支持事务吗:


在缺省模式下,MYSQL 是 autocommit 模式的,所有的数据库更新操作都会即时提交,所
以在缺省情况下,mysql 是不支持事务的。
但是如果你的 MYSQL 表类型是使用 InnoDB Tables 或 BDB tables 的话,你的 MYSQL 就可以
使用事务处理,使用 SET AUTOCOMMIT=0 就可以使 MYSQL 允许在非 autocommit 模式,在非
autocommit 模式下,你必须使用 COMMIT 来提交你的更改,或者用 ROLLBACK 来回滚你的
更改。

MYSQL 数据表在什么情况下容易损坏:


服务器突然断电导致数据文件损坏。
强制关机,没有先关闭 mysql 服务等。

Mysql 中有哪几种锁:


MyISAM 支持表锁,InnoDB 支持表锁和行锁,默认为行锁。


表级锁:开销小,加锁快,不会出现死锁。锁定粒度大,发生锁冲突的概率最高,并发量最低。
行级锁:开销大,加锁慢,会出现死锁。锁力度小,发生锁冲突的概率小,并发度最高

mysql 有关权限的表都有哪几个:


Mysql 服务器通过权限表来控制用户对数据库的访问,权限表存放在 mysql 数据库里,由
mysql_install_db 脚本初始化。这些权限表分别 user,db,table_priv,columns_priv 和
host。

mysql 里记录货币用什么字段类型好:
NUMERIC 和 DECIMAL 类型被 Mysql 实现为同样的类型,这在 SQL92 标准允许。他们被用于
保存值,该值的准确精度是极其重要的值,例如与金钱有关的数据。当声明一个类是这些
类型之一时,精度和规模的能被(并且通常是)指定;

sql having 的使用场景:


如果需要对组函数的结果作为条件,那么不能使用where子句,必须使用having子句

MySQL 分页查询语句:

LIMIT [offset,] rows
offset指定要返回的第一行的偏移量,rows第二个指定返回行的最大数目

删除重复记录,保存Id最小的一条:

delete FROM `test` WHERE `school_name` in (SELECT `school_name`

FROM `test`

GROUP BY `school_name`

HAVING COUNT( * ) >1) and school_id not in (select min(school_id) from test group by school_id having count(* )>1)

先使用GROUP BY having语法查询出重复的数据,然后删除重复数据并保留school_id最小的一条.

删除重复记录,保存Id最大的一条:

delete FROM `test` WHERE `school_name` in (SELECT `school_name`

FROM `test`

GROUP BY `school_name`

HAVING COUNT( * ) >1) and school_id not in (select max(school_id) from test group by school_id having count(* )>1)

如果要存ip地址,用什么数据类型比较好:

建议当存储IPv4地址时应该使用32位的无符号整数(UNSIGNED INT)来存储IP地址,而不是使用字符串。

相对字符串存储,使用无符号整数来存储有如下的好处:

节省空间不管是数据存储空间,还是索引存储空间 便于使用范围查询(BETWEEN...AND),且效率更高 通常。在保存IPv4地址时,一个IPv4最小需要7个字符,最大需要15个字符,所以,使用VARCHAR(15)即可。MySQL在保存变长的字符串时,还需要额外的一个字节来保存此字符串的长度。而如果使用无符号整数来存储,只需要4个字节即可。另外还可以使用4个字段分别存储IPv4中的各部分,但是通常这不管是存储空间和查询效率应该都不是很高(可能有的场景适合使用这种方式存储)。

使用无符号整数来存储也有缺点:

不便于阅读
需要手动转换
对于转换来说,MySQL提供了相应的函数来把字符串格式的IP转换成整数INET_ATON,以及把整数格式的IP转换成字符串的INET_NTOA。如下所示:

select inet_aton('192.168.0.1');
select inet_ntoa(3232235521);

对于IPv6来说,使用VARBINARY同样可获得相同的好处,同时MySQL也提供了相应的转换函数,即INET6_ATON和INET6_NTOA。


对于转换字符串IPv4和数值类型,可以放在应用层。

public class IpLongUtils {
    /**
     * 把字符串IP转换成long
     *
     * @param ipStr 字符串IP
     * @return IP对应的long值
     */
    public static long ip2Long(String ipStr) {
        String[] ip = ipStr.split("\\.");
        return (Long.valueOf(ip[0]) << 24) + (Long.valueOf(ip[1]) << 16)
                + (Long.valueOf(ip[2]) << 8) + Long.valueOf(ip[3]);
    }

    /**
     * 把IP的long值转换成字符串
     *
     * @param ipLong IP的long值
     * @return long值对应的字符串
     */
    public static String long2Ip(long ipLong) {
        StringBuilder ip = new StringBuilder();
        ip.append(ipLong >>> 24).append(".");
        ip.append((ipLong >>> 16) & 0xFF).append(".");
        ip.append((ipLong >>> 8) & 0xFF).append(".");
        ip.append(ipLong & 0xFF);
        return ip.toString();
    }

    public static void main(String[] args) {
        System.out.println(ip2Long("192.168.0.1"));
        System.out.println(long2Ip(3232235521L));
        System.out.println(ip2Long("10.0.0.1"));
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值