1.MYSQL基础
视图
一种虚拟存在的表,对于使用视图的用户来说基本上是透明的。视图并不实际存在,行和列数据来自定义视图的查询中的使用表,并且是在使用视图时动态生存。
视图的优势:简单、安全、独立
存储过程和函数
存储过程和函数是事先经过编译并存储在数据库中的一段SQL语句的集合,调用存储过程和函数可以简化应用开发人员的很多工作,减少数据在数据和应用服务器之间的传输,对于提高数据处理的效率是有好处的。
存储过程和函数的区别在于函数必须有函数址,而存储过程没有,存储过程参数可以水用IN、OUT、INOUT类型,而函数的参数只能是IN类型的。
触发器
触发其是与表有关的数据库对象,在满足定义条件时触发,并执行处罚器中定义的语句结合。触发其德这种特性可以协助应用在数据库端确保数据的完整性。
锁粒度
各种MySQL存储引擎都可以实现自己的锁策略和锁粒度。
表锁
MySQL基本策略,锁定整张表。写锁比读锁有更高的优先级。
行级锁
在数据存储层实现。
事务
事务就是一组原子性的SQL查询,或者说是一个独立的单元。事务内的语句要么全部执行成功,要么全部执行失效。
ACID:
原子性:事务被视为一个不可分割的最小单元,要么全部成功,要么全部失败回滚。
一致性:数据库总是从一个一致性状态转化到另一个一致性状态。
隔离性:一个事务所做的修改在最终提交以前,对其他事务是不可见的。
持久性:一旦事务提交,则其所做的修改就会永久保存到数据库中。
隔离级别
四种隔离级别
1、未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
2、提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)
3、可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读,但是innoDB解决了幻读
4、串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞
未提交读:事务可以读物为提交的数据,也即是脏读。某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
提交读:出现不可重复读问题。在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
可重复读:幻读,指的是某个事务在读取某个范围内的记录,另一个事务又在该范围内插入新的记录,当前的事务读取该范围内的记录,会产生幻行。MySQL默认隔离级事务。
可串行读:每一行数据加锁。导致大量的超时和锁争用。
幻读和不可重复读的区别?
幻读是比不可重复读高一个级别的错误,读取同一条数据发现跟刚才是一样的,只有读取一堆数据发现忽然多了一个,或者少了一个,就像自己养的一堆柯基莫名其妙多出来一只,刚才明明检查过没有这只的呀,是不是产生了幻觉!
解决幻读问题,需要引入多版本并发控制,或者整个表加锁(显然不可取)。
多版本并发控制
为了提高性能,数据库都提供了多版本并发控制(MVCC),避免了加锁操作;是通过保存数据在莫讴歌时间点的快找来实现的。
MVCC的实现有很多,最典型的有乐观并发控制和悲观并发控制。
悲观并发控制:
一个锁定系统,可以阻止用户以影响其他用户的方式修改数据。如果用户执行的操作导致应用了某个锁,只有这个锁的所有者释放该锁,其他用户才能执行与该锁冲突的操作。这种方法之所以称为悲观并发控制,是因为它主要用于数据争用激烈的环境中,以及发生并发冲突时用锁保护数据的成本低于回滚事务的成本的环境中。
乐观并发控制:
在乐观并发控制中,用户读取数据时不锁定数据。当一个用户更新数据时,系统将进行检查,查看该用户读取数据后其他用户是否又更改了该数据。如果其他用户更新了数据,将产生一个错误。一般情况下,收到错误信息的用户将回滚事务并重新开始。这种方法之所以称为乐观并发控制,是由于它主要在以下环境中使用:数据争用不大且偶尔回滚事务的成本低于读取数据时锁定数据的成本。
MVCC在可重复读中的实现,在每行记录中保存两个隐藏的实现,一个保存行的创建时间(系统版本号),一个是过期时间(删除时间)。
SELECT语句:
1.InnoDB只查找版本早于当前事务版本的数据行
2.行的删除版本要么未定义要么大于当前事务版本号
INSERT:
InnoDB为新插入的每一行保存当前系统版本号座位行版本号
DELETE:
InnoDB为删除的每一行保存当前版本号作为行删除标识。
UPDATE:
InnoDB插入一行新纪录,保存当前版本号为行版本号和行删除标识。
read view(或者说 MVCC)实现了一致性不锁定读(Consistent Nonlocking Reads),从而避免了幻读
2.MYSQL引擎
mysql中常用引擎,InnoDB存储引擎和MyISAM存储引擎。
InnoDB存储引擎:
1.数据存储形式:
使用InnoDB时,会将数据表分为.frm(表结构) 和 idb(存储表数据和索引)两个文件进行存储
2.锁的粒度
InnoDB采用MVCC(多版本并发控制)来支持高并发,InnoDB实现了四个隔离级别,默认级别是REPETABLE READ,并通过间隙锁策略防止幻读的出现。它的锁粒度是行锁。
3.事务
InnoDB是典型的事务型存储引擎,并且通过一些机制和工具,支持真正的热备份。
4.数据的存储特点
InnoDB表是基于聚簇索引(另一篇博客有介绍)建立的,聚簇索引对主键的查询有很高的性能,不过他的二级索引(非主键索引)必须包含主键列,索引其他的索引会很大
MyISAM存储引擎
1.数据存储形式:
MyISAM采用的是索引与数据分离的形式,将数据保存在三个文件中.frm(表结构).MYD(数据),.MYIs(索引)。
2.锁的粒度
MyISAM不支持行锁,所以读取时对表加上共享锁,在写入是对表加上排他锁。由于是对整张表加锁,相比InnoDB,在并发写入时效率很低。
3.事务
MyISAM不支持事务。
4.数据的存储特点
MyISAM是基于非聚簇索引进行存储的
5.其他
MyISAM提供了大量的特性,包括全文索引,压缩,空间函数,延迟更新索引键等。
进行压缩后的表是不能进行修改的,但是压缩表可以极大减少磁盘占用空间,因此也可以减少磁盘IO,从而提供查询性能。
全文索引,是一种基于分词创建的索引,可以支持复杂的查询。
延迟更新索引键,不会将更新的索引数据立即写入到磁盘,而是会写到内存中的缓冲区中,只有在清除缓冲区时候才会将对应的索引写入磁盘,这种方式大大提升了写入性能
3.MYSQL中的索引
索引是在MYSQL的存储引擎层中实现的,而不是在服务层实现的。所以每种存储引擎的索引都不一定完全相同,也不是所有的存储引擎都支持所有的索引类型。MYSQL目前提供了一下4种索引。
- B-Tree 索引:最常见的索引类型,大部分引擎都支持B树索引。
- HASH 索引:只有Memory引擎支持,使用场景简单。
- R-Tree 索引(空间索引):空间索引是MyISAM的一种特殊索引类型,主要用于地理空间数据类型。
- Full-text (全文索引):全文索引也是MyISAM的一种特殊索引类型,主要用于全文索引,InnoDB从MYSQL5.6版本提供对全文索引的支持。
一 索引选择原则
1. 较频繁的作为查询条件的字段应该创建索引
2. 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
3. 更新非常频繁的字段不适合创建索引
4. 不会出现在 WHERE 和 group by ordery by 子句中的字段不该创建索引
一 索引类型
innodb的主键索引都是聚簇索引,聚簇索引把数据行放在索引数据结构的叶子节点上,一个表只能有一个聚簇索引. 如果没有主键, Innodb会选择一个唯一的非空索引代替,如果没有这样的索引,Innodb会隐含的定义一个主键来作为聚簇索引.
注意: 上图中非叶子节点只保存了索引列,叶子节点保存了索引列和数据行.
聚簇索引的优点如下:
1, 数据访问更快, 聚簇索引把数据和索引保存在同一个B-tree中,索引命中即意味中数据全部命中.
2, 使用覆盖索引的扫描的查询可以直接使用页节点中的主键值.
缺点如下:
1, 聚簇索引最大限度的提高了I/O密集型应用的性能.
2, 插入速度严重依赖插入顺序.
3, 更新聚簇索引列的代价很高,因为需要移位.
4, 插入新数据时,或者主键裂需要移动的时候,可能面临“页分裂”.
5, 非聚簇索引(二级索引)包含了聚簇索引,如果聚簇索引较大,二级索引也会很大(二级索引的叶子节点包含了主键列的值).
innodb的二级索引是直接在叶子节点存储了聚簇索引的值.
二级索引的叶子节点保存的不是指向行的物理位置指针,而是行的主键值.这样行的在面临页分裂时,不需要单独维护二级索引.
而MyISAM存储引擎则是主键索引和其它二级索引一样都指向了行数据.
MySQL主要提供2种方式的索引:B-Tree(包括B+Tree)索引,Hash索引。
B树索引具有范围查找和前缀查找的能力,对于N节点的B树,检索一条记录的复杂度为O(LogN)。
哈希索引只能做等于查找,但是无论多大的Hash表,查找复杂度都是O(1)。
显然,如果值的差异性大,并且以等于查找为主,Hash索引是更高效的选择,它有O(1)的查找复杂度。如果值的差异性相对较差,并且以范围查找为主,B树是更好的选择,它支持范围查找。
4.SQL优化建议
所有的优化无非1个原则,尽量避免全表扫描(建立索引本身也是这个原因),避免在SQL语句上进行一些操作,如果有需要再程序中处理。
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=0
3.应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
4.应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num=10 or num=20
可以这样查询:
select id from t where num=10
union all
select id from t where num=20
5.in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
6.下面的查询也将导致全表扫描:
select id from t where name like '%abc%'
7.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
应改为:
select id from t where num=100*2
8.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)='abc'--name以abc开头的id
应改为:
select id from t where name like 'abc%'
9.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
10.在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,
否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
11.不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0
这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:
create table #t(...)
12.很多时候用 exists 代替 in 是一个好的选择:
select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)
13.并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,
如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。
14.索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,
因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。
一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。
15.尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。
这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
16.尽可能的使用 varchar 代替 char ,因为首先变长字段存储空间小,可以节省存储空间,
其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
17.任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
18.避免频繁创建和删除临时表,以减少系统表资源的消耗。
19.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
20.在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,
以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
21.如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
22.尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。
23.使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
24.与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。
在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
25.尽量避免大事务操作,提高系统并发能力。
26.尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。