mysql架构
mysql架构

MySQL最重要的、最与众不同的特性是它的存储引擎架构,这种构架的设计将查询处理及其它系统任务和数据的存储/提取相分离。

为什么需要锁?因为数据库要解决并发控制问题。
在同一时刻,可能会有多个客户端对表中同一行记录进行操作,比如有的在读取该行数据,其他的尝试去删除它。为了保证数据的一致性,数据库就要对这种并发操作进行控制,因此就有了锁的概念。

乐观锁和悲观锁以及时间戳

并发控制一般采用三种方法,分别是乐观锁和悲观锁以及时间戳

乐观锁认为一个用户读数据的时候,别人不会去写自己所读的数据;

悲观锁就刚好相反,觉得自己读数据库的时候,别人可能刚好在写自己刚读的数据,其实就是持一种比较保守的态度;
悲观锁就是在读取数据的时候,为了不让别人修改自己读取的数据,就会先对自己读取的数据加锁,只有自己把数据读完了,才允许别人修改那部分数据,或者反过来说,就是自己修改某条数据的时候,不允许别人读取该数据,只有等自己的整个事务提交了,才释放自己加上的锁,才允许其他用户访问那部分数据。

并发量较大时候选择悲观锁,并发量较小的时候选择乐观锁

时间戳就是不加锁,通过时间戳来控制并发出现的问题。时间戳就是在数据库表中单独加一列时间戳,比如“TimeStamp”,每次读出来的时候,把该字段也读出来,当写回去的时候,把该字段加1,提交之前 ,跟数据库的该字段比较一次,如果比数据库的值大的话,就允许保存,否则不允许保存,这种处理方法虽然不使用数据库系统提供的锁机制,但是这种方法可以大大提高数据库处理的并发量,因为这种方法可以避免了长事务中的数据库加锁开销(操作员A 和操作员B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系 统整体性能表现。

悲观锁

以上悲观锁所说的加“锁”,其实分为3种锁

共享(S)锁:多个事务可封锁一个共享页;任何事务都不能修改该页; 通常是该页被读取完毕,S锁立即被释放。

排它(X)锁:仅允许一个事务封锁此页;其他任何事务必须等到X锁被释放才能对该页进行访问;X锁一直到事务结束才能被释放。

更新(U)锁:更新锁在修改操作的初始化阶段用来锁定可能要被修改的资源,这样可以避免使用共享锁造成的死锁现象。

因为使用共享锁时,修改数据的操作分为两步,首先获得一个共享锁,读取数据,然后将共享锁升级为排它锁,然后再执行修改操作。
这样如果同时有两个或多个事务同时对一个事务申请了共享锁,在修改数据的时候,这些事务都要将共享锁升级为排它锁。这时,这些事务都不会释放共享锁而是一直等待对方释放,这样就造成了死锁。在update语句中很容易出现死锁现象,比如t1和t2都要更新id=100的记录,那么可能存在t1先获取读锁,然后t2再获取读锁,然后t1要得写锁,同时t2也要获得写锁,这时候就会产生死锁。因为t1获得写锁的前提是t2释放读锁,而t2获得写锁的前提是t1释放读锁。
所以为了防止这种情的发生,一个数据在修改前直接申请更新锁,在数据修改的时候再升级为排它锁,就可以避免死锁。

更新锁有如下特征:

  • 加锁的条件:当一个事务执行update语句时,数据库系统会先为事务分配一把更新锁。
  • 解锁的条件:当读取数据完毕,执行更新操作时,会把更新锁升级为独占锁。
  • 与其他锁的兼容性:更新锁与共享锁是兼容的,也就是说,一个资源可以同时放置更新锁和共享锁,但是最多放置一把更新锁。这样,当多个事务更新相同的数据时,只有一个事务能获得更新锁,然后再把更新锁升级为独占锁,其他事务必须等到前一个事务结束后,才能获取得更新锁,这就避免了死锁。

默认来说,当sql脚本修改更新某条记录的时候,会给该条记录加X锁,读的话加的是S锁
对于 UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X);
对于普通SELECT语句,InnoDB不会加任何锁;
事务可以通过显式给记录集加共享锁或排他锁。

锁粒度(Lock granularity)

为了尽可能提高数据库的并发度,每次锁定的数据范围越小越好,理论上每次只锁定当前操作的数据的方案会得到最大的并发度,但是管理锁是很耗资源的事情(涉及获取,检查,释放锁等动作),因此数据库系统需要在高并发响应和系统性能两方面进行平衡,这样就产生了“锁粒度(Lock granularity)”的概念。

一种提高共享资源并发发性的方式是让锁定对象更有选择性。
尽量只锁定需要修改的部分数据,而不是所有的资源。
更理想的方式是,只对会修改的数据片进行精确的锁定。任何时候,在给定的资源上,锁定的数据量越少,则系统的并发程度越高,只要相互之间不发生冲突即可。
但是,加锁也需要消耗资源。锁的各种操作,包括获得锁、检查锁和是否已经解除、释放锁等,都会增加系统的开销。
所谓锁策略,就是在锁的开销和数据的安全性之间寻求平衡。

表锁与行锁

表锁:管理锁的开销最小,同时允许的并发量也最小的锁机制。
MyIsam存储引擎使用表锁。当要写入数据时,把整个表都锁上,此时其他读、写动作一律等待。
在MySql中,除了MyIsam存储引擎使用这种锁策略外,MySql本身也使用表锁来执行某些特定动作,比如alter table.

另外,写锁比读锁有更高的优先级,因此一个写锁可能会被插入到读锁队列的前面。

行锁:可以支持最大并发的锁策略(同时也带来了最大的锁开销)。
InnoDB和Falcon两张存储引擎都采用行锁。行级锁只在存储引擎层实现,而MySQL服务器层没有实现。服务器层完全不了解存储引擎中的锁实现。

MySql是一种开放的架构,你可以实现自己的存储引擎,并实现自己的锁粒度策略,不像Oracle,你没有机会改变锁策略,Oracle采用的是行锁。

多版本并发控制 MVCC

在并发读写数据库时,读操作可能会不一致的数据(脏读)。为了避免这种情况,需要实现数据库的并发访问控制,最简单的方式就是加锁访问。
由于,加锁会将读写操作串行化,所以不会出现不一致的状态。但是,读操作会被写操作阻塞,大幅降低读性能。
在Java concurrent包中,有copyonwrite系列的类,专门用于优化读远大于写的情况。而其优化的手段就是,在进行写操作时,将数据copy一份,不会影响原有数据,然后进行修改,修改完成后原子替换掉旧的数据,而读操作只会读取原有数据。通过这种方式实现写操作不会阻塞读操作,从而优化读效率。而写操作之间是要互斥的,并且每次写操作都会有一次copy,所以只适合读大于写的情况。

MVCC的原理与copyonwrite类似,全称是Multiversion Concurrency Controll,即多版本并发控制。在MVCC协议下,每个读操作会看到一个一致性的snapshot,并且可以实现非阻塞的读。MVCC允许数据具有多个版本,这个版本可以是时间戳或者是全局递增的事务ID,在同一个时间点,不同的事务看到的数据是不同的。
由此可以看出MVCC是一种用来解决读-写冲突的无锁并发控制.

MVCC策略:
MVCC并不是MySql独有的,Oracle,PostgreSQL等都在使用。
MVCC并没有简单地使用行锁,而是使用“行级别锁”(row-level locking)。

MVCC的基本原理是:
MVCC的实现,通过保存数据在某个时间点的快照来实现的。
这意味着一个事务无论运行多长时间,在同一个事务里能够看到数据一致的视图。
根据事务开始的时间不同,同时也意味着在同一个时刻不同事务看到的相同表里的数据可能是不同的。

MVCC的基本特征:
每行数据都存在一个版本,每次数据更新时都更新该版本。
修改时Copy出当前版本随意修改,各个事务之间无干扰。
保存时比较版本号,如果成功(commit),则覆盖原记录;失败则放弃copy(rollback)

InnoDB存储引擎MVCC的实现策略:
在每一行数据中额外保存两个隐藏的列:当前行创建时的版本号和删除时的版本号(可能为空)。
这里的版本号并不是实际的时间值,而是系统版本号。
每开始个新的事务,系统版本号都会自动递增。事务开始时刻的系统版本号会作为事务的版本号,用来和查询每行记录的版本号进行比较。
每个事务又有自己的版本号,这样事务内执行CRUD操作时,就通过版本号的比较来达到数据版本控制的目的。

MVCC
MVCC

MVCC具体的操作如下:

SELECT:InnoDB会根据以下两个条件检查每行记录,只有满足两个条件才会返回:

1)InnoDB只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,只么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。

2)行的删除版本要么未定义,要么大于当前事务版本号。这可以确保事务读取到的行,在事务开始之前未被删除。

INSERT:InnoDB为新插入的每一行保存当前系统版本号作为行版本号。

DELETE:InnoDB为删除的每一行保存当前系统版本号作为行删除标识。

UPDATE:InnoDB为插入一行新记录,保存当前系统版本号作为行版本号,同时保存当系统的版本号为原来的行作为删除标识。

保存这两个额外系统版本号,使大多数操作都可以不用加锁。这样设计使得计数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作,以及一些额外的维护工作。

MVCC只在REPEATABLE READ和READ COMMITED两个隔离级别下工作,其它两个隔离级别和MVCC不兼容。

Innodb的实现算不上MVCC,因为并没有实现核心的多版本共存,undo log中的内容只是串行化的结果,记录了多个事务的过程,不属于多版本共存。但理想的MVCC是难以实现的,当事务仅修改一行记录使用理想的MVCC模式是没有问题的,可以通过比较版本号进行回滚;但当事务影响到多行数据时,理想的MVCC据无能为力了。

比如,如果Transaciton1执行理想的MVCC,修改Row1成功,而修改Row2失败,此时需要回滚Row1,但因为Row1没有被锁定,其数据可能又被Transaction2所修改,如果此时回滚Row1的内容,则会破坏Transaction2的修改结果,导致Transaction2违反ACID。

理想MVCC难以实现的根本原因在于企图通过乐观锁代替二段提交。修改两行数据,但为了保证其一致性,与修改两个分布式系统中的数据并无区别,而二段提交是目前这种场景保证一致性的唯一手段。二段提交的本质是锁定,乐观锁的本质是消除锁定,二者矛盾,故理想的MVCC难以真正在实际中被应用,Innodb只是借了MVCC这个名字,提供了读的非阻塞而已。
http://blog.csdn.net/chen77716/article/details/6742128
http://blog.csdn.net/xifeijian/article/details/45230053

两阶段封锁

InnoDB采用两阶段锁协议(2PL:Two-phase Locking Protocol)实现串行化。
在事务内任意时刻加锁,最后提交或回滚时一起释放所有锁

一次性锁协议:事务开始时,一次性申请所有的锁,之后不会再申请任何锁。如果其中某个锁不可用,则整个申请就不成功,事务就不会执行,在事务尾端,一次性释放所有的锁。一次性锁协议不会产生死锁的问题,但事务的并发度不高。

两阶段锁协议(跟分布式事务XA的两阶段提交)具体如下:
两阶段锁协议:整个事务分为两个阶段,前一个阶段为加锁,后一个阶段为解锁。在加锁阶段,事务只能加锁,也可以操作数据,但不能解锁。直到事务释放第一个锁,就进入解锁阶段,此过程中事务只能解锁,也可以操作数据,不能再加锁。
两阶段锁协议使得事务具有较高的并发度,因为解锁不必发生在事务结尾。它的不足是没有解决死锁的问题,因为它在加锁阶段没有顺序要求。如两个事务分别申请了A, B锁,接着又申请对方的锁,此时进入死锁状态。

加锁的情况

在一个支持MVCC并发控制的系统中,哪些读操作是快照读?哪些操作又是当前读呢?以MySQL InnoDB为例:

快照读:简单的select操作,属于快照读,不加锁。(当然,也有例外,下面会分析)
select * from table where ?;
当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values (…);
update table set ? where ?;
delete from table where ?;
所有以上的语句,都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其他的操作,都加的是X锁 (排它锁)。

分析加锁
SQL1:select * from t1 where id = 10;
SQL2:delete from t1 where id = 10;

在RC,RR隔离级别下,SQL1:select操作均不加锁,采用的是快照读,因此在下面的讨论中就忽略了,主要讨论SQL2:delete操作的加锁。

组合一:id主键+RC
结论:id是主键时,此SQL只需要在id=10这条记录上加X锁即可。

组合二:id唯一索引+RC
若id列是unique列,其上有unique索引。那么SQL需要加两个X锁,一个对应于id unique索引上的id = 10的记录,另一把锁对应于聚簇索引上的[name=’d’,id=10]的记录。

组合三:id非唯一索引+RC
结论:若id列上有非唯一索引,那么对应的所有满足SQL查询条件的记录,都会被加锁。同时,这些记录在主键索引上的记录,也会被加锁。

组合四:id无索引+RC
结论:若id列上没有索引,SQL会走聚簇索引的全扫描进行过滤,由于过滤是由MySQL Server层面进行的。因此每条记录,无论是否满足条件,都会被加上X锁。但是,为了效率考量,MySQL做了优化,对于不满足条件的记录,会在判断后放锁,最终持有的,是满足条件的记录上的锁,但是不满足条件的记录上的加锁/放锁动作不会省略。同时,优化也违背了2PL的约束。

组合五:id主键+RR
组合五,id列是主键列,Repeatable Read隔离级别,针对delete from t1 where id = 10; 这条SQL,加锁与组合一:[id主键,Read Committed]一致。

组合六:id唯一索引+RR
与组合五类似,组合六的加锁,与组合二:[id唯一索引,Read Committed]一致。两个X锁,id唯一索引满足条件的记录上一个,对应的聚簇索引上的记录一个。

组合七:id非唯一索引+RR
结论:Repeatable Read隔离级别下,id列上有一个非唯一索引,对应SQL:delete from t1 where id = 10; 首先,通过id索引定位到第一条满足查询条件的记录,加记录上的X锁,加GAP上的GAP锁,然后加主键聚簇索引上的记录X锁,然后返回;然后读取下一条,重复进行。直至进行到第一条不满足条件的记录[11,f],此时,不需要加记录X锁,但是仍旧需要加GAP锁,最后返回结束。

组合八:id无索引+RR
结论:在Repeatable Read隔离级别下,如果进行全表扫描的当前读,那么会锁上表中的所有记录,同时会锁上聚簇索引内的所有GAP,杜绝所有的并发 更新/删除/插入 操作。当然,也可以通过触发semi-consistent read,来缓解加锁开销与并发影响,但是semi-consistent read本身也会带来其他问题,不建议使用。

组合九:Serializable
针对前面提到的简单的SQL,最后一个情况:Serializable隔离级别。对于SQL2:delete from t1 where id = 10; 来说,Serializable隔离级别与Repeatable Read隔离级别完全一致,因此不做介绍。
Serializable隔离级别,影响的是SQL1:select * from t1 where id = 10; 这条SQL,在RC,RR隔离级别下,都是快照读,不加锁。但是在Serializable隔离级别,SQL1会加读锁,也就是说快照读不复存在,MVCC并发控制降级为Lock-Based CC。
结论:在MySQL/InnoDB中,所谓的读不加锁,并不适用于所有的情况,而是隔离级别相关的。Serializable隔离级别,读不加锁就不再成立,所有的读操作,都是当前读。

认真读:什么情况加锁

死锁(Dead Lock)

死锁是指两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的假象。多个事务同时锁定同一个资源时,也会产生死锁。
数据库系统实现了各种死锁检测和死锁超时的机制,InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚。

并发控制会造成活锁和死锁,就像操作系统那样,会因为互相等待而导致。
活锁指的是T1封锁了数据R,T2同时也请求封锁数据R,T3也请求封锁数据R,当T1释放了锁之后,T3会锁住R,T4也请求封锁R,则T2就会一直等待下去,这种处理方法就是采用“先来先服务”策略;
死锁就是我等你,你又等我,双方就会一直等待下去,比如:T1封锁了数据R1,正请求对R2封锁,而T2封住了R2,正请求封锁R1,这样就会导致死锁,死锁这种没有完全解决的方法,只能尽量预防.

预防死锁的方法有:
①一次封锁发,指的是一次性把所需要的数据全部封锁住,但是这样会扩大了封锁的范围,降低系统的并发度;
②顺序封锁发,指的是事先对数据对象指定一个封锁顺序,要对数据进行封锁,只能按照规定的顺序来封锁,但是这个一般不大可能的。
另外,系统如何判断出现死锁,毕竟出现死锁不能一直干等下去,要及时发现死锁同时尽快解决出现的死锁,

诊断和判断死锁有两种方法:
1)超时法:超时法就是如果某个事物的等待时间超过指定时限,则判定为出现死锁;
2)等待图法:等待图法指的是如果事务等待图中出现了回路,则判断出现了死锁。

对于解决死锁的方法,只能是撤销一个处理死锁代价最小的事务,释放此事务持有的所有锁,同时对撤销的事务所执行的数据修改操作必须加以恢复。

数据库系统实现了各种死锁检测和死锁超时的机制,InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚。

事务(Transaction)

事务ACID原则

从业务角度出发,对数据库的一组操作要求保持4个特征:

Atomicity(原子性):一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。

Consistency(一致性):一致性保证能看到系统内的所有改变。
一致性是指事务使得系统从一个一致的状态转换到另一个一致状态。事务的一致性决定了一个系统设计和实现的复杂度。事务可以不同程度的一致性:
强一致性:读操作可以立即读到提交的更新操作。
弱一致性:提交的更新操作,不一定立即会被读操作读到,此种情况会存在一个不一致窗口,指的是读操作可以读到最新值的一段时间。
最终一致性:是弱一致性的特例。事务更新一份数据,最终一致性保证在没有其他事务更新同样的值的话,最终所有的事务都会读到之前事务更新的最新值。如果没有错误发生,不一致窗口的大小依赖于:通信延迟,系统负载等。
其他一致性变体还有:
单调一致性:如果一个进程已经读到一个值,那么后续不会读到更早的值。
会话一致性:保证客户端和服务器交互的会话过程中,读操作可以读到更新操作后的最新值。

Isolation(隔离性):通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。注意这里的“通常来说”,后面的事务隔离级级别会说到。
可以将隔离性理解为以性能为理由,对一致性进行破坏。

Durability(持久性):一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。(持久性的安全性与刷新日志级别也存在一定关系,不同的级别对应不同的数据安全级别。)
数据为了保证持久性使用raid技术来冗余数据,raid主要解决了两个问题:①如何保证数据同时写到两个磁盘上去②由于每次请求都要刷磁盘会导致性能下降,因此要将请求打包同意发送到存储 。
down机恢复,要进入recovery模式,不能读写

隔离级别(Isolation Level)

SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。
低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。

Read Uncommitted(未提交读)
在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

Read Committed(提交读)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。

Repeatable Read(可重复读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。
一种更易理解的说法是:在一个事务内,多次读同一个数据。在这个事务还没有结束时,另一个事务也访问该同一数据。那么,在第一个事务的两次读数据之间。由于第二个事务的修改,那么第一个事务读到的数据可能不一样,这样就发生了在一个事务内两次读到的数据是不一样的,因此称为不可重复读,即原始读取不可重复。

不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。
InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

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

事务的隔离级别
事务的隔离级别

这四种隔离级别采取不同的锁类型来实现,若读取的是同一个数据的话,就容易发生问题。例如:

脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。

不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。

幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。

不可重复读重点在于update和delete,而幻读的重点在于insert。
例子:http://www.hollischuang.com/archives/900

在MySQL中,实现了这四种隔离级别,分别有可能产生问题如下所示:

脏读 不可重复读 幻读
未提交读 Y Y Y
提交读 N Y Y
可重复读 N N Y
可串行化 N N N

索引

索引是在存储引擎中实现的,而不是在服务器层中实现的。所以,每种存储引擎的索引都不一定完全相同,并不是所有的存储引擎都支持所有的索引类型

b树索引

B-Tree:每一个叶子节点都包含指向下一个叶子节点的指针,从而方便叶子节点的范围遍历。B-Tree通常意味着所有的值都是按顺序存储的,并且每一个叶子页到根的距离相同,很适合查找范围数据。

不同的b树索引实现

MyISAM引擎使用B+Tree作为索引结构,叶节点的data域存放的是数据记录的地址。
MyISAM索引文件和数据文件是分离的,索引文件仅保存数据记录的地址。

MyISAM索引
MyISAM索引

在InnoDB中完全不同:
第一个重大区别是InnoDB的数据文件本身就是索引文件。而在InnoDB中,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。
InnoDB索引
InnoDB索引

这张图中的主键索引就是聚集索引

注意:辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录

索引的查询

b树索引
b树索引

其索引包含表中每一行的last_name、first_name和dob列。

索引存储的值按索引列中的顺序排列。可以利用B-Tree索引进行全关键字、关键字范围和关键字前缀查询
如果想使用索引,你必须保证按索引的最左边前缀(leftmost prefix of the index) 来进行查询。
(1)匹配全值(Match the full value):对索引中的所有列都指定具体的值。例如,上图中索引可以帮助你查找出生于1960-01-01的Cuba Allen。
(2)匹配最左前缀(Match a leftmost prefix):你可以利用索引查找last name为Allen的人,仅仅使用索引中的第1列。
(3)匹配列前缀(Match a column prefix):例如,你可以利用索引查找last name以J开始的人,这仅仅使用索引中的第1列。
(4)匹配值的范围查询(Match a range of values):可以利用索引查找last name在Allen和Barrymore之间的人,仅仅使用索引中第1列。
(5)匹配部分精确而其它部分进行范围匹配(Match one part exactly and match a range on another part):可以利用索引查找last name为Allen,而first name以字母K开始的人。
(6)仅对索引进行查询(Index-only queries):如果查询的列都位于索引中,则不需要读取元组的值。(覆盖索引)

由于B-树中的节点都是顺序存储的,所以可以利用索引进行查找(找某些值),也可以对查询结果进行ORDER BY。
当然,使用B-tree索引有以下一些限制:
(1) 查询必须从索引的最左边的列开始,否则无法使用索引。关于这点已经提了很多遍了。例如你不能利用索引查找在某一天出生的人。
(2) 不能跳过某一索引列。例如,你不能利用索引查找last name为Smith且出生于某一天的人。
(3) 存储引擎不能使用索引中范围条件右边的列。例如,如果你的查询语句为WHERE last_name=”Smith” AND first_name LIKE ‘J%’ AND dob=’1976-12-23’,则该查询只会使用索引中的前两列,因为LIKE是范围查询。

聚簇索引

聚簇索引(Clustered Indexes)
聚簇索引保证关键字的值相近的元组存储的物理位置也相同(所以字符串类型不宜建立聚簇索引,特别是随机字符串,会使得系统进行大量的移动操作),且一个表只能有一个聚簇索引
因为由存储引擎实现索引,所以,并不是所有的引擎都支持聚簇索引。目前,只有solidDB和InnoDB支持。
聚簇索引的结构大致如下:
叶子页包含了行的全部数据,但是节点页只包含了索引列。
二级索引叶子节点保存的不是指行的物理位置的指针,而是行的主键值。这意味着通过二级索引查找行,存储引擎需要找到二级索引的叶子节点获取对应的主键值,然后根据这个值去聚簇索引中查找到对应的行。这里做了重复的工作:两次B-TREE查找而不是一次。

b树索引
b树索引

InnoDB对主键建立聚簇索引。如果你不指定主键,InnoDB会用一个具有唯一且非空值的索引来代替。如果不存在这样的索引,InnoDB会定义一个隐藏的主键,然后对其建立聚簇索引。

http://blog.csdn.net/xifeijian/article/details/20312557

查询优化

mysql查询过程
mysql查询过程

MySQL的优化主要分为结构优化(Scheme optimization)和查询优化(Query optimization)

sql语句方面的优化

  • join做索引
  • 尽量不用null,全部设置为not null,避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描
  • 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
  • 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
  • 尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描
  • 应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描
    select id from t where num/2=100应改为:select id from t where num=100*2
  • 应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。
    如:select id from t where substring(name,1,3)=’abc’select id from t where datediff(day,createdate,’2005-11-30′)=0–’2005-11-30′应改为:
    select id from t where name like ‘abc%’select id from t where createdate>=’2005-11-30′ and createdate<’2005-12-1′

索引失效情况:

  • like语句中%前置可能会导致索引失效
  • 如果条件中有or,即使其中有条件带索引也不会使用(这也是为什么尽量少用or的原因)
  • 对于多列索引,不是使用的第一部分,则不会使用索引
  • 如果列类型是字符串,那一定要在条件中将数据使用引号引用起来,否则不使用索引

http://www.uml.org.cn/sjjm/201610184.asp

使用查询缓存

大多数的MySQL服务器都开启了查询缓存。这是提高性最有效的方法之一,而且这是被MySQL的数据库引擎处理的。当有很多相同的查询被执行了多次的时候,这些查询结果会被放到一个缓存中,这样,后续的相同的查询就不用操作表而直接访问缓存结果了。
使用函数时要注意,因为像 NOW() 和 RAND() 或是其它的诸如此类的SQL函数都不会开启查询缓存,因为这些函数的返回是会不定的易变的。
http://blog.csdn.net/xifeijian/article/details/45320605

经常查询的做索引

索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值:
Index Selectivity = Cardinality / #T
显然选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的。
SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles;
http://blog.codinglabs.org/articles/theory-of-mysql-index.html

使用explain做优化

explain的含义,type 字段比较重要, 它提供了判断查询是否高效的重要依据依据. 通过 type 字段, 我们判断此次查询是 全表扫描 还是 索引扫描 等.
通常来说, 不同的 type 类型的性能关系如下:
ALL < index < range ~ index_merge < ref < eq_ref < const < system
ALL 类型因为是全表扫描, 因此在相同的查询条件下, 它是速度最慢的.
而 index 类型的查询虽然不是全表扫描, 但是它扫描了所有的索引, 因此比 ALL 类型的稍快.
后面的几种类型都是利用了索引来查询数据, 因此可以过滤部分或大部分数据, 因此查询效率就比较高了.

https://segmentfault.com/a/1190000008131735

使用正确的索引引擎

MyISAM 适合于一些需要大量查询的应用,但其对于有大量写操作并不是很好。甚至你只是需要update一个字段,整个表都会被锁起来,而别的进程,就算是读进程都无法操作直到读操作完成。另外,MyISAM 对于 SELECT COUNT(*)这类的计算是超快无比的。

InnoDB 的趋势会是一个非常复杂的存储引擎,对于一些小的应用,它会比 MyISAM 还慢。他是它支持“行锁” ,于是在写操作比较多的时候,会更优秀。并且,他还支持更多的高级应用,比如:事务。

http://blog.csdn.net/xifeijian/article/details/45287139
http://blog.codinglabs.org/articles/theory-of-mysql-index.html

一些定义

DQL,DML,DDL,DCL

DQL数据查询语言:其语句,也称为“数据检索语句”,用以从表中获得数据,确定数据怎样在应用程序给出。保留字SELECT是DQL(也是所有SQL)用得最多的动词,其他DQL常用的保留字有WHERE,ORDER BY,GROUP BY和HAVING。这些DQL保留字常与其他类型的SQL语句一起使用。

DML(data manipulation language)是数据操纵语言:UPDATE、INSERT、DELETE,当执行DML命令如果没有提交,将不会被其他会话看到。除非在DML命令之后执行了DDL命令或DCL命令,或用户退出会话,或终止实例,此时系统会自动发出commit命令,使未提交的DML命令提交。

DDL(data definition language)是数据定义语言:DDL比DML要多,主要的命令有CREATE、ALTER、DROP等,DDL主要是用在定义或改变表(TABLE)的结构,数据类型,表之间的链接和约束等初始化工作上,他们大多在建立表时使用。当执行DDL语句时,在每一条语句前后,oracle都将提交当前的事务。如果用户使用insert命令将记录插入到数据库后,执行了一条DDL语句(如create table),此时来自insert命令的数据将被提交到数据库。当DDL语句执行完成时,DDL语句会被自动提交,不能回滚。

DCL(Data Control Language)是数据库控制语言:是用来设置或更改数据库用户或角色权限的语句,包括(grant,deny,revoke等)语句。

因为DDL没有事务性,所以DDL不能回滚。要实现自动回滚。(begin,commit,rollback),则SQL语句中只能包括DML。

MySQL默认操作模式就是autocommit自动提交模式。这就表示除非显式地开始一个事务,否则每个查询都被当做一个单独的事务自动执行。

mysqldump导出数据库

SQL truncate 、delete与drop区别

相同点:
1.truncate和不带where子句的delete、以及drop都会删除表内的数据。
2.drop、truncate都是DDL语句(数据定义语言),执行后会自动提交。

不同点:
1.truncate 和 delete 只删除数据不删除表的结构(定义)
drop 语句将删除表的结构被依赖的约束(constrain)、触发器(trigger)、索引(index);依赖于该表的存储过程/函数将保留,但是变为 invalid 状态。
2.delete 语句是数据库操作语言(dml),这个操作会放到 rollback segement 中,事务提交之后才生效;如果有相应的 trigger,执行的时候将被触发。
truncate、drop 是数据库定义语言(ddl),操作立即生效,原数据不放到 rollback segment 中,不能回滚,操作不触发 trigger。
3.速度,一般来说: drop> truncate > delete
DELETE语句执行删除操作的过程是每次从表中删除一行,并且同时将该行的删除操作作为事务记录在日志中保存以便进行进行回滚操作。
TRUNCATE TABLE 则一次性地从表中删除所有的数据并不把单独的删除操作记录记入日志保存,删除行是不能恢复的。并且在删除的过程中不会激活与表有关的删除触发器。执行速度快。
4.安全性:小心使用 drop 和 truncate,尤其没有备份的时候.否则哭都来不及
使用上,想删除部分数据行用 delete,注意带上where子句. 回滚段要足够大.
想删除表,当然用 drop
想保留表而将所有数据删除,如果和事务无关,用truncate即可。如果和事务有关,或者想触发trigger,还是用delete。

primary key,key,index,unique

primary key是主键,一个表中只能有一个主键,mysql会为其建造聚集索引
unique表示不会重复,有唯一约束,一个表中可以有多个unique
mysql中key和index是同义的,是索引的意思,没有唯一约束
key,index,unique建造的都是B+树索引

视图

视图可以定义在多张表上,因此定义功能比表强。
视图中数据更新受到诸多限制,例如不能有聚集函数,不能是定义在多张表上等,因此操作功能弱于表。
视图的数据控制功能和表的数据控制功能相当,都有GRANT、REVOKE。

连接 join

左外连接的结果集包括 LEFT OUTER 子句中指定的左表的所有行,而不仅仅是连接列所匹配的行
右外连接是左向外连接的反向连接。将返回右表的所有行。
全外连接返回左表和右表中的所有行。当某行在另一个表中没有匹配行时,则另一个表的选择列表列包含空值。如果表之间有匹配行,则整个结果集行包含基表的数据值。 (类似于求并集)
内连接使用比较运算符根据每个表共有的列的值匹配两个表中的行。(类似于求交集)

mysql中的join
mysql中的join

join的原则:小表驱动大表,即小表放在外层
http://www.kuqin.com/database/20081206/29717.html
http://www.cnblogs.com/zhengyun_ustc/p/slowquery1.html

having、group by、

● 聚合语句avg,min等必须要和group联合使用

● 需要注意having和where的用法区别:
1.having只能用在group by之后,对分组后的结果进行筛选(即使用having的前提条件是分组)。
2.where肯定在group by 之前
3.where后的条件表达式里不允许使用聚合函数,而having可以。

SELECT COUNT(CustomerID), Country
FROM Customers
where City like ‘_o%’
GROUP BY Country
HAVING COUNT(CustomerID) > 5 limit 10;

● 当一个查询语句同时出现了where,group by,having,order by的时候,执行顺序和编写顺序是:wghso我哥好帅哦
1.执行where xx对全表数据做筛选,返回第1个结果集。
2.针对第1个结果集使用group by分组,返回第2个结果集。
3.针对第2个结果集中的每1组数据执行select xx,有几组就执行几次,返回第3个结果集。
4.针对第3个结集执行having xx进行筛选,返回第4个结果集。
5.针对第4个结果集排序。

FROM & JOINs determine & filter rows
WHERE more filters on the rows
GROUP BY combines those rows into groups
HAVING filters groups
ORDER BY arranges the remaining rows/groups
LIMIT filters on the remaining rows/groups

http://stackoverflow.com/questions/1130062/what-is-the-execution-sequence-of-group-by-having-and-where-clause-in-sql-server

范式

1NF:原子性 字段不可再分,否则就不是关系数据库;
2NF:唯一性 一个表只说明一个事物;
3NF:每列都与主键有直接关系,不存在传递依赖;
https://www.zhihu.com/question/24696366

分页功能

利用limit关键字

1
2
3
4
5
6
mysql> SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15
//为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:
mysql> SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.
//如果只给定一个参数,它表示返回最大的记录行数目:
mysql> SELECT * FROM table LIMIT 5; //检索前 5 个记录行
//换句话说,LIMIT n 等价于 LIMIT 0,n。

事务调优原则

1、尽可能较小锁的覆盖范围
2、尽可能让多线程并发访问数据
3、并发度不高使用乐观锁,并发度较高使用悲观锁