概念:事务(Transaction)是一个操作序列,不可分割的工作单位,以BEGIN TRANSACTION开始,以ROLLBACK/COMMIT结束
特性(ACID):
原子性(Atomicity):逻辑上是不可分割的操作单元,事务的所有操作要么全部提交成功,要么全部失败回滚(用回滚日志实现,反向执行日志中的操作);
一致性(Consistency):事务的执行必须使数据库保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的;
隔离性(Isolation):一个事务所做的修改在最终提交以前,对其它事务是不可见的(并发执行的事务之间不能相互影响);
持久性(Durability):一旦事务提交成功,对数据的修改是永久性的
丢失修改:一个事务对数据进行了修改,在事务提交之前,另一个事务对同一个数据进行了修改,覆盖了之前的修改;
脏读(Dirty Read):一个事务读取了被另一个事务修改、但未提交(进行了回滚)的数据,造成两个事务得到的数据不一致;
不可重复读(Nonrepeatable Read):在同一个事务中,某查询操作在一个时间读取某一行数据和之后一个时间读取该行数据,发现数据已经发生修改(针对update操作);
幻读(Phantom Read):当同一查询多次执行时,由于其它事务在这个数据范围内执行了插入操作,会导致每次返回不同的结果集(和不可重复读的区别:针对的是一个数据整体/范围;并且针对insert操作)
未提交读(READ-UNCOMMITTED):最低的隔离级别,允许脏读,也就是可能读取到其他会话中未提交事务修改的数据,可能会导致脏读、幻读或不可重复读。
提交读(READ-COMMITTED): 只能读取到已经提交的数据。Oracle 等大多数数据库默认都是该级别 (不重复读),可以阻止脏读,但是幻读或不可重复读仍有可能发生
可重复度读(REPEATABLE-READ):对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
可串行化(SERIALIZABLE):最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
⚫ MySQL 默认采用的 REPEATABLE_READ(可重复读) 隔离级别。
可重复读的核心就是一致性读(consistent read);保证多次读取同一个数据时,其值都和事
务开始时候的内容是一致,禁止读取到别的事务未提交的数据,会造成幻读。
而事务更新数据的时候,只能用当前读。如果当前的记录的行锁被其他事务占用的话,就
需要进入锁等待。
查询只承认在事务启动前就已经提交完成的数据。
可重复读解决的是重复读的问题,可重复读在快照读的情况下是不会有幻读,但当前读的
时候会有幻读。
分析语句,是否加载了不必要的字段/数据。
分析 SQL 执行计划(explain extended),思考可能的优化点,是否命中索引等。
查看 SQL 涉及的表结构和索引信息。
如果 SQL 很复杂,优化 SQL 结构。
按照可能的优化点执行表结构变更、增加索引、SQL 改写等操作。
查看优化后的执行时间和执行计划。
如果表数据量太大,考虑分表。
利用缓存,减少查询次数。
悲观锁:认为数据随时会被修改,因此每次读取数据之前都会上锁,防止其它事务读取或修改数据;应用于数据更新比较频繁的场景;
乐观锁:操作数据时不会上锁,但是更新时会判断在此期间有没有别的事务更新这个数据,若被更新过,则失败重试;适用于读多写少的场景。乐观锁的实现方式有:
加一个版本号或者时间戳字段,每次数据更新时同时更新这个字段;
先读取想要更新的字段或者所有字段,更新的时候比较一下,只有字段没有变化才进行更新
聚集索引是指数据库表行中数据的物理顺序与键值的逻辑(索引)顺序相同。一个表只能有一
个聚簇索引,因为一个表的物理顺序只有一种情况,所以,对应的聚簇索引只能有一个。聚簇
索引的叶子节点就是数据节点,既存储索引值,又在叶子节点存储行数据。
哈希虽然能够提供 O(1) 的单数据行操作性能,但是对于范围查询和排序却无法很好地支
持,最终导致全表扫描;B 树能够在非叶节子点中存储数据,但是这也导致在查询连续数
据时可能会带来更多的随机 I/O,而 B+树的所有叶节点可以通过指针相互连接,能够减
少顺序遍历时产生的额外随机 I/O;
第一,B 树一个节点里存的是数据,而 B+树存储的是索引(地址),所以 B 树里一个节
点存不了很多个数据,但是 B+树一个节点能存很多索引,B+树叶子节点存所有的数据。
第二,B+树的叶子节点是数据阶段用了一个链表串联起来,便于范围查找。
MVCC,多版本并发控制,它是通过读取历史版本的数据,来降低并发事务冲突,从而提高并
发性能的一种机制。MVCC在每行记录后面都保存有两个隐藏的列,用来存储创建版本号和删除版本号
创建版本号:创建一个数据行时的事务版本号(事务版本号:事务开始时的系统版本号;系统版本号:每开始一个新的事务,系统版本号就会自动递增);
删除版本号:删除操作时的事务版本号;
各种操作:
插入操作时,记录创建版本号;
删除操作时,记录删除版本号;
更新操作时,先记录删除版本号,再新增一行记录创建版本号;
查询操作时,要符合以下条件才能被查询出来:删除版本号未定义或大于当前事务版本号(删除操作是在当前事务启动之后做的);创建版本号小于或等于当前事务版本号(创建操作是事务完成或者在事务启动之前完成)
通过版本号减少了锁的争用,提高了系统性能;可以实现提交读和可重复读两种隔离级别,未提交读无需使用MVCC
意向锁是 InnoDB 自动加的, 不需用户干预。
对于 UPDATE、 DELETE 和 INSERT 语句, InnoDB会自动给涉及数据集加排他锁(X);
对于普通 SELECT 语句,InnoDB 不会加任何锁;
事务可以通过以下语句显式给记录集加共享锁或排他锁:
共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。 其他 session 仍然可以查询记录,并也可以对该记录加 share mode 的共享锁。但是如果当前事务需要对该记录进行更新操作,则很有可能造成死锁。
排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE。其他 session 可以查询该记录,但是不能对该记录加共享锁或排他锁,而是等待获得锁
排它锁(Exclusive Lock)/ X锁:事务对数据加上X锁时,只允许此事务读取和修改此数据,并且其它事务不能对该数据加任何锁;
共享锁(Shared Lock)/ S锁:加了S锁后,该事务只能对数据进行读取而不能修改,并且其它事务只能加S锁,不能加X锁
意向锁(Intention Locks):
一个事务在获得某个数据行对象的 S 锁之前,必须先获得整个表的 IS 锁或更强的锁;
一个事务在获得某个数据行对象的 X 锁之前,必须先获得整个表的 IX 锁;
IS/IX 锁之间都是兼容的;
好处:如果一个事务想要对整个表加X锁,就需要先检测是否有其它事务对该表或者该表中的某一行加了锁,这种检测非常耗时。有了意向锁之后,只需要检测整个表是否存在IX/IS/X/S锁就行了
锁的作用:用于管理对共享资源的并发访问,保证数据库的完整性和一致性
一级封锁协议:事务在修改数据之前必须先对其加X锁,直到事务结束才释放。可以解决丢失修改问题(两个事务不能同时对一个数据加X锁,避免了修改被覆盖);
二级封锁协议:在一级的基础上,事务在读取数据之前必须先加S锁,读完后释放。可以解决脏读问题(如果已经有事务在修改数据,就意味着已经加了X锁,此时想要读取数据的事务并不能加S锁,也就无法进行读取,避免了读取脏数据);
三级封锁协议:在二级的基础上,事务在读取数据之前必须先加S锁,直到事务结束才能释放。可以解决不可重复读问题(避免了在事务结束前其它事务对数据加X锁进行修改,保证了事务期间数据不会被其它事务更新)
举个栗子:
对于 insert 类型的 sql,会在 undo log 中记录下方才你 insert 进来的数据的 ID,当你想roll back 时,根据 ID 完成精准的删除。
对于 delete 类型的 sql,会在 undo log 中记录方才你删除的数据,当你回滚时会将删除前的数据 insert 进去。
对于 update 类型的 sql,会在 undo log 中记录修改前的数据,回滚时只需反向update 即可。
对于 select 类型的 sql,别费心了,select 不需要回滚。
第一范式(1NF,Normal Form):属性不应该是可分的。举例:如果将“电话”作为一个属性(一列),是不符合1NF的,因为电话这个属性可以分解为家庭电话和移动电话...如果将“移动电话”作为一个属性,就符合1NF;
第二范式 2NF:每个非主属性完全依赖于主属性集(候选键集);
B完全依赖于A,就是说A中的所有属性唯一决定B,属性少了就不能唯一决定,属性多了则有冗余(叫依赖不叫完全依赖)。举例:(学号,课程名)这个主属性集可以唯一决定成绩,但是对于学生姓名这个属性,(学号,课程名)这个属性集就是冗余的,所以学生姓名不完全依赖于(学号,课程名)这一属性集;
主属性集/候选码集:某一组属性能够唯一确定其它的属性(主键就是从候选键集中选的一个键),而其子集不能,这样的属性组中的属性就是主属性;不在候选码集中的属性成为非主属性;
可以通过分解来满足 2NF:将(学号,课程名,成绩)做成一张表;(学号,学生姓名)做成另一张表,避免大量的数据冗余;
满足1NF后,要求表中的所有列,都必须依赖于主键,而不能有任何一列与主键没有关系,也就是说一个表只描述一件事情;
第三范式 3NF:在 2NF 的基础上,非主属性不传递依赖于主属性
传递依赖:如果C依赖于B,B依赖于A,那么C传递依赖于A;
3NF在2NF的基础上,消除了非主属性之间的依赖;比如一个表中,主属性有(学号),非主属性有(姓名,院系,院长名),可以看到院长名这个非主属性依赖于院系,传递依赖于学号。消除的办法是分解。
必须先满足第二范式(2NF),要求:表中的每一列只与主键直接相关而不是间接相关,(表中的每一列只能依赖于主键);
冗余数据:某些同样的数据多次出现(如学生姓名);
修改异常:修改了一个记录中的信息,另一个记录中相同的信息却没有修改;
删除异常:删除一个信息,那么也会丢失其它信息(删除一个课程,丢失了一个学生的信息);
插入异常:无法插入(插入一个还没有课程信息的学生)
内连接(Inner Join):仅将两个表中满足连接条件的行组合起来作为结果集
自然连接:只考虑属性相同的元组对;
等值连接:给定条件进行查询
外连接(Outer Join)
左连接:左边表的所有数据都有显示出来,右边的表数据只显示共同有的那部分,没有对应的部分补NULL;
右连接:和左连接相反;
全外连接(Full Outer Join):查询出左表和右表所有数据,但是去除两表的重复数据
交叉连接(Cross Join):返回两表的笛卡尔积(对于所含数据分别为m、n的表,返回m*n的结果)
存储过程是事先经过编译并存储在数据库中的一段SQL语句的集合。想要实现相应的功能时,只需要调用这个存储过程就行了(类似于函数,输入具有输出参数)。
优点:
预先编译,而不需要每次运行时编译,提高了数据库执行效率;
封装了一系列操作,对于一些数据交互比较多的操作,相比于单独执行SQL语句,可以减少网络通信量;
具有可复用性,减少了数据库开发的工作量;
安全性高,可以让没有权限的用户通过存储过程间接操作数据库;
更易于维护
缺点:
可移植性差,存储过程将应用程序绑定到了数据库上;
开发调试复杂:没有好的IDE;
修改复杂,需要重新编译,有时还需要更新程序中的代码以更新调用
Delete用来删除表的全部或者部分数据,执行delete之后,用户需要提交之后才会执行,会触发表上的DELETE触发器(包含一个OLD的虚拟表,可以只读访问被删除的数据),DELETE之后表结构还在,删除很慢,一行一行地删,因为会记录日志,可以利用日志还原数据;
Truncate删除表中的所有数据,这个操作不能回滚,也不会触发这个表上的触发器。操作比DELETE快很多(直接把表drop掉,再创建一个新表,删除的数据不能找回)。如果表中有自增(AUTO_INCREMENT)列,则重置为1;
Drop命令从数据库中删除表,所有的数据行,索引和约束都会被删除;不能回滚,不会触发触发器;
触发器(TRIGGER)是由事件(比如INSERT/UPDATE/DELETE)来触发运行的操作(不能被直接调用,不能接收参数)。在数据库里以独立的对象存储,用于保证数据完整性(比如可以检验或转换数据)。
约束(Constraint)类型:主键(Primary Key)约束,唯一约束(Unique),检查约束,非空约束,外键(Foreign Key)约束。
视图:从数据库的基本表中通过查询选取出来的数据组成的虚拟表(数据库中存放视图的定义)。可以对其进行增/删/改/查等操作。视图是对若干张基本表的引用,一张虚表,查询语句执行的结果,不存储具体的数据(基本表数据发生了改变,视图也会跟着改变);可以跟基本表一样,进行增删改查操作(ps:增删改操作有条件限制);如连表查询产生的视图无法进行,对视图的增删改会影响原表的数据。好处:
通过只给用户访问视图的权限,保证数据的安全性;
简化复杂的SQL操作,隐藏数据的复杂性(比如复杂的连接);
游标(Cursor):用于定位在查询返回的结果集的特定行,以对特定行进行操作。使用游标可以方便地对结果集进行移动遍历,根据需要滚动或对浏览/修改任意行中的数据。主要用于交互式应用。
InnoDB的索引使用的是B+树实现,B+树对比B树的好处:
IO次数少:B+树的中间结点只存放索引,数据都存在叶结点中,因此中间结点可以存更多的数据,让索引树更加矮胖;
范围查询效率更高:B树需要中序遍历整个树,只B+树需要遍历叶结点中的链表;
查询效率更加稳定:每次查询都需要从根结点到叶结点,路径长度相同,所以每次查询的效率都差不多
哈希索引能以 O(1) 时间进行查找,但是只支持精确查找,无法用于部分查找和范围查找,无法用于排序与分组;B树索引支持大于小于等于查找,范围查找。哈希索引遇到大量哈希值相等的情况后查找效率会降低。哈希索引不支持数据的排序。
大大加快了数据的检索速度;
可以显著减少查询中分组和排序的时间;
通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性;
将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,会将相邻的数据都存储在一起)
缺点:建立和维护索引耗费时间空间,更新索引很慢。
某列经常作为最大最小值;
经常被查询的字段;
经常用作表连接的字段;
经常出现在ORDER BY/GROUP BY/DISDINCT后面的字段
只应建立在小字段上,而不要对大文本或图片建立索引(一页存储的数据越多一次IO操作获取的数据越大效率越高);
建立索引的字段应该非空,在MySQL中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。应该用0、一个特殊的值或者一个空串代替NULL;
选择数据密度大(唯一值占总数的百分比很大)的字段作索引
普通索引
唯一索引 UNIQUE:索引列的值必须唯一,但允许有空值;
主键索引 PRIMARY KEY:必须唯一,不允许空值(是一种特殊的唯一索引;MySQL创建主键时默认为聚集索引,但主键也可以是非聚集索引);
单列索引和多列索引/复合索引(Composite):索引的列数;
覆盖(Covering)索引:索引包含了所有满足查询所需要的数据,查询的时候只需要读取索引而不需要回表读取数据;
聚集(Clustered)索引/非聚集索引:对磁盘上存放数据的物理地址重新组织以使这些数据按照指定规则排序的一种索引(数据的物理排列顺序和索引排列顺序一致)。因此每张表只能创建一个聚集索引(因为要改变物理存储顺序)。优点是查询速度快,因为可以直接按照顺序得到需要数据的物理地址。缺点是进行修改的速度较慢。对于需要经常搜索范围的值很有效。非聚集索引只记录逻辑顺序,并不改变物理顺序;
虚拟索引(Virtual):模拟索引的存在而不用真正创建一个索引,用于快速测试创建索引对执行计划的影响。没有相关的索引段,不增加存储空间的使用
InnoDB支持事务,可以进行Commit和Rollback;
MyISAM 只支持表级锁,而 InnoDB 还支持行级锁,提高了并发操作的性能;
InnoDB 支持外键;
MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢;
MyISAM 支持压缩表和空间数据索引,InnoDB需要更多的内存和存储;
InnoDB 支持在线热备份
MyISAM 管理非事务表。它提供高速存储和检索(MyISAM强调的是性能,每次查询具有原子性,其执行速度比InnoDB更快),以及全文搜索能力。如果表比较小,或者是只读数据(有大量的SELECT),还是可以使用MyISAM;
InnoDB 支持事务,并发情况下有很好的性能,基本可以替代MyISAM
热备份:在数据库运行的情况下备份的方法。优点:可按表或用户备份,备份时数据库仍可使用,可恢复至任一时间点。但是不能出错
冷备份:数据库正常关闭后,将关键性文件复制到另一位置的备份方式。优点:操作简单快速,恢复简单
分析慢查询日志:记录了在MySQL中响应时间超过阀值long_query_time的SQL语句,通过日志去找出IO大的SQL以及发现未命中索引的SQL
使用 Explain 进行分析:通过explain命令可以得到表的读取顺序、数据读取操作的操作类型、哪些索引可以使用、 哪些索引被实际使用、表之间的引用以及 被扫描的行数等问题;
应尽量避免在 where 子句中使用!=、<、>操作符或对字段进行null值判断,否则将引擎放弃使用索引而进行全表扫描;
只返回必要的列:最好不要使用 SELECT * 语句;
只返回必要的行:使用 LIMIT 语句来限制返回的数据;
将一个大连接查询分解成对每一个表进行一次单表查询,然后在应用程序中进行关联,这样做的好处有:
让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用;
分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余的查询;
减少锁竞争
注意会引起索引失效的情况,以及在适合的地方建立索引
设计表时遵循三大范式;
选择合适的数据类型:尽可能不要存储NULL字段;使用简单的数据类型(int, varchar/ text);
表的水平切分(Sharding):将同一个表中的记录拆分到多个结构相同的表中(策略:哈希取模;根据ID范围来分)。当一个表的数据不断增多时,Sharding 是必然的选择,它可以将数据分布到集群的不同节点上,从而缓解单个数据库的压力;
表的垂直切分:将一张表按列切分成多个表。可以将不常用的字段单独放在同一个表中;把大字段独立放入一个表中;或者把经常使用的字段(关系密切的)放在一张表中。垂直切分之后业务更加清晰,系统之间整合或扩展容易,数据维护简单
操作系统:增加TCP支持的队列数;
MySQL配置文件优化:缓存池大小和个数设置
磁盘性能:固态硬盘;
CPU:多核且高频;
内存:增大内存
主从复制(Replication)是指数据可以从一个MySQL数据库主服务器复制到一个或多个从服务器,从服务器可以复制主服务器中的所有数据库或者特定的数据库,或者特定的表。默认采用异步模式。
实现原理:
主服务器 binary log dump 线程:将主服务器中的数据更改(增删改)日志写入 Binary log 中;
从服务器 I/O 线程:负责从主服务器读取binary log,并写入本地的 Relay log;
从服务器 SQL 线程:负责读取 Relay log,解析出主服务器已经执行的数据更改,并在从服务器中重新执行(Replay),保证主从数据的一致性
读写分离:主服务器负责写,从服务器负责读
缓解了锁的争用,即使主服务器中加了锁,依然可以进行读操作;
从服务器可以使用 MyISAM,提升查询性能以及节约系统开销;
增加冗余,提高可用性
数据实时备份,当系统中某个节点发生故障时,可以方便的故障切换
降低单个服务器磁盘I/O访问的频率,提高单个机器的I/O性能
前者高度组织化结构化数据;后者存储的数据结构不固定更加灵活,可以减少一些空间和时间的开销
后者更加容易水平扩展
前者支持结构化查询语言,支持复杂的查询功能和表关联。后者只能进行简单的查询
前者支持事务,具有ACID特性。后者则是BASE,最终一致性
like 以%开头索引无效,当 like 以&结尾,索引有效。
or 语句前后没有同时使用索引,当且仅当 or 语句查询条件的前后列均为索引时,索引生效。
组合索引,使用的不是第一列索引时候,索引失效,即最左匹配规则。
数据类型出现隐式转换,如 varchar 不加单引号的时候可能会自动转换为 int 类型,这个时候索引失效。
在索引列上使用 IS NULL 或者 IS NOT NULL 时候,索引失效,因为索引是不索引空值得。
在索引字段上使用,NOT、 <>、!= 、时候是不会使用索引的,对于这样的处理只会进行全表扫描。
对索引字段进行计算操作,函数操作时不会使用索引
当全表扫描速度比索引速度快的时候不会使用索引