赞
踩
什么是索引
优势与劣势
优势:
劣势:
CREATE TABLE table_name [col_name data_type ]
[ UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY] [index_name] (col_name [length])[ASC | DESc]
-- 创建普通索引
create table book(
book_id int,
book_name varchar(50),
authors varchar(50),
info varchar(50),
comment varchar(50),
year_publication year,
index idx_bname(book_name) -- 声明索引,字段为book_name
);
ALTER TABLE table_name ADD
[UNIQUE | FULLTEXT | SPATIAL][INDEX | KEY] [index_name] (col_name [length] ,...)[ASC | DESC]
-- 创建普通索引
alter table book2 add index idx_cmt(comment);
-- 创建唯一索引
alter table book2 add unique uk_idx_bname(book_name);
-- 创建联合索引
alter table book2 add index mul_bid_bname_info(book_id,book_name,info);
create index idx_cmt on book3(comment);-- 创建普通索引
create unique index uk_idx_bname on book3(book_name);-- 创建唯一索引
create index mul_bid_bname_info on book3(book_id,book_name,info);-- 创建联合索引
ALTER TABLE table_name DROP INDEX index_name;
-- 示例
alter table book3 drop index idx_cmt;
alter table book3 drop index mul_bid_bname_info;
DROP INDEX index_name on table_name;
--示例
drop index idx_cmt on book2;
drop index mul_bid_bname_info on book2;
索引是在MySQL的存储引擎层中实现的,而不是在服务器层实现的。MySQL目前提供了以下4种索引:
索引 | InnoDB引擎 | MyISAM引擎 | Memory引擎 |
---|---|---|---|
BTREE索引 | 支持 | 支持 | 支持 |
HASH 索引 | 不支持 | 不支持 | 支持 |
R-tree 索引 | 不支持 | 支持 | 不支持 |
Full-text | 5.6版本之后支持 | 支持 | 不支持 |
基于哈希表实现
,只有精确匹配索引所有列的查询才有效
, 对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码(hash code)
,并且Hash索引将所有的哈希码存储在索引中, 同时在索引表中保存指向每个数据行的指针。
BTree又叫 多路平衡搜索树
,一颗m叉的BTree特性如下:
ceil为向上取整
B-Tree是为磁盘等外存储设备设计的一种平衡查找树
。因此在讲B-Tree之前先了解下磁盘的相关知识。
系统从磁盘读取数据到内存时是以 磁盘块(block)
为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来
,而不是需要什么取什么。
InnoDB存储引擎中有 页(Page)的概念
, 页是其磁盘管理的最小单位
。InnoDB存储引擎中 默认每个页的大小为16KB
,可通过参数innodb_page_size将页的大小设置为4K、8K、16K,在MySQL中可通过如下命令查看页的大小:
mysql> show variables like 'innodb_page_size'
而系统一个磁盘块的存储空间往往没有这么大,因此InnoDB每次申请磁盘空间时都会是若干地址连续磁盘块来达到页的大小16KB。InnoDB在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。
B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。为了描述B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键值,data为一行记录中除主键外的数据。对于不同的记录,key值互不相同。B-Tree中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个 3阶的B-Tree
:
每个节点占用一个盘块的磁盘空间
,一个节点上有两个升序排序的关键字
和三个指向子树根节点的指针
,指针存储的是子节点所在磁盘块的地址
。两个关键词划分成的三个范围域
对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。
模拟查找关键字29的过程:
读入内存
。【磁盘I/O操作第1次
】读入内存
。【磁盘I/O操作第2次
】读入内存
。【磁盘I/O操作第3次
】在磁盘块8中的关键字列表中找到关键字29
。分析上面过程,发现 需要3次磁盘I/O操作,和3次内存查找操作
。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率
。而 3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素
。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。
MySQL中B+Tree相对于BTree的特点
由于B+Tree只有叶子节点保存key信息,查询任何key都要从root走到叶子。所以B+Tree的查询效率更加稳定。
将上一节中的B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示:
通常在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点
(啥意思????????),而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+Tree进行两种查找运算:
可能上面例子中只有22条数据记录,看不出B+Tree的优点,下面做一个推算:InnoDB存储引擎中页的大小为16KB
因为是估值,为方便计算,这里的K取值为 10 ^ 3
)。也就是说一个深度为3的B+Tree索引可以维护 10^3 * 10^3 * 10^3 = 10亿
条记录(啥意思,为啥是相乘????????)。一个磁盘块代表一次IO
,很明显数据量多的情况下,IO次数也会多,会影响查询性能,于是在B树的基础上衍生出了B+树。实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的高度一般都在2~4层。mysql的InnoDB存储引擎在设计时 是将根节点常驻内存的
,也就是说查找某一键值的行记录时 最多只需要1 - 3次磁盘I/O操作
。
B+ 树是基于 B 树和叶子节点顺序访问指针进行实现,它具有 B 树的平衡性,并且通过顺序访问指针来提高区间查询的性能,数据都在叶子节点上。
从BTree结构图中可以看到 每个节点中
不仅包含数据的 key值
,还有data值
。而每一个页的存储空间是有限的:
深度较大
,增大查询时的磁盘I/O次数
,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息
,这样 可以大大加大每个节点存储的key值数量,降低B+Tree的高度
。
用 B+ 树不用 B 树考虑的是 IO 对性能的影响,B 树的每个节点都存储数据,而 B+ 树只有叶子节点才存储数据。
由于B树的分支结点存储着数据,我们要找到具体的数据,需要 进行一次中序遍历
按序来扫。而由于B+树的数据都 存储在叶子结点中
,叶子结点均为索引,方便扫库, 只需要扫一遍叶子结点即可
。
所以B+树更加适合在区间查询的情况,而在数据库中基于范围的查询是非常频繁的,所以B+树更适合用于数据库索引。
B树可以在内部节点同时存储键和值,因此把频繁访问的数据放 在靠近根节点的地方
将会大大提高热点数据的查询效率。 这种特性使得B树在特定数据重复多次查询的场景中更加高效
。
这先要从InnoDB的索引实现说起,InnoDB有两大类索引:
聚集索引严格来说并不是索引类型,而是一种数据存储方式,具体细节依赖于其实现方式。如innodb聚集索引的叶子节点存放了整张表的行记录(B+Tree)。将数据存储与索引放到了一块,找到索引也就找到了数据。
InnoDB聚集索引的叶子节点存储行记录,因此, InnoDB必须要有,且只有一个聚集索引:
将数据与索引分开存储,索引结构的叶子节点指向了数据的对应行
。对于InnoDB来说,想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为回表。
第一次索引一般是顺序IO,回表的操作属于随机IO。需要回表的次数越多,即随机IO次数越多,我们就越倾向于使用全表扫描 。上在InnoDB引擎中,非主键索引查找数据时需要先找到主键,再根据主键查找具体行数据,这种现象叫回表查询。
假设表中有四条记录:1, shenjian, m, A;3, zhangsan, m, A;5, lisi, m, A;9, wangwu, f, B
两个B+树索引分别如上图:
查询:select * from t where name=‘lisi’;
如粉红色路径,需要扫码两遍索引树:
这就是所谓的回表查询,先定位主键值,再定位行记录
,它的性能较扫一遍索引树更低。
当查询使用聚簇索引时,在对应的叶子节点,可以获取到整行数据,因此不用再次进行回表查询。
如果涉及到查询语句所要求的字段全部命中了索引,那么就不必再进行回表查询。
一个索引包含(覆盖)所有需要查询字段的值,被称之为"覆盖索引"。
一个索引包含(覆盖)所有需要查询字段的值,被称之为"覆盖索引"。具有以下优点:
创建复合索引:
CREATE INDEX idx_name_email_status ON tb_seller(a,b,c);
就相当于
对a创建索引 ;
对a, b创建了索引 ;
对a, b, c创建了索引 ;
只要查询条件包含了name,就会利用索引查询
b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,b+数是按照从左到右的顺序来建立搜索树的。
比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;
但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。
比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
因为可能我们索引的字段非常长,这既占内存空间,也不利于维护。所以我们就想,如果只把很长字段的前面的公共部分作为一个索引 ,就会产生超级加倍的效果。但是,我们需要注意,order by不支持前缀索引 。
流程是:
通过explain关键字,如以下例子:
EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title='SeniorEngineer' AND from_date='1986-06-26';
全表扫描
还是 索引扫描
等。
ALL < index < range ~ index_merge < ref < eq_ref < const < system
1、全值匹配 ,对索引中所有列都指定具体值。
2、最左前缀法则,**如果索引了多列,要遵守最左前缀法则。指的是查询从索引的最左前列开始,并且不跳过索引中的列。
3、不要在索引列上进行运算操作, 索引将失效。
4、字符串不加单引号,造成索引失效。
5、尽量使用覆盖索引,避免select * ,如果查询列,超出索引列,也会降低性能。
6、用or分割开的条件, 如果or前的条件中的列有索引,而后面的列中没有索引,那么涉及的索引都不会被用到。
7、以 %开头的Like模糊查询,索引失效。解决方案 :通过覆盖索引来解决,这样即使是模糊查询也会走索引。
8、如果MySQL评估使用索引比全表更慢,则不使用索引。
9、is NULL , is NOT NULL 有时 索引失效。比如:一个字段都不为空,那么使用NOT NULL还不如走全表扫描,mysql会自动判断。
10、in 走索引, not in 索引失效。
11、单列索引和复合索引。尽量使用复合索引,而少使用单列索引 。针对单列索引,数据库会选择一个最优的索引(辨识度最高索引
)来使用,并不会使用全部索引 。
索引下推(index condition pushdown )简称ICP,在Mysql5.6的版本上推出,用于优化查询。
在不使用ICP的情况下,在使用非主键索引(又叫普通索引或者二级索引)进行查询时, 存储引擎通过索引检索到数据,然后返回给MySQL服务器,由服务器然后判断数据是否符合条件 。
在使用ICP的情况下,如果存在某些被索引的列的判断条件时,MySQL服务器将这一部分判断条件传递给存储引擎,然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件,只有当索引符合条件时才会将数据检索出来返回给MySQL服务器。
索引条件下推优化可以减少存储引擎查询基础表的次数,也可以减少MySQL服务器从存储引擎接收数据的次数。
1) 连接层:最上层是一些客户端和链接服务,包含本地sock 通信和大多数基于客户端/服务端工具实现的类似于 TCP/IP的通信。主要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
2) 服务层:第二层架构主要完成大多数的核心服务功能,如 SQL接口,并完成缓存的查询,SQL的分析和优化,部分内置函数的执行。 所有跨存储引擎的功能也在这一层实现,如 过程、函数等。 在该层,服务器会解析查询并创建相应的内部解析树,并对其完成相应的优化如确定表的查询的顺序,是否利用索引等, 最后生成相应的执行操作。
如果是select语句,服务器还会查询内部的缓存,如果缓存空间足够大,这样在解决大量读操作的环境中能够很好的提升系统的性能。
3) 引擎层:存储引擎层, 存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API和存储引擎进行通信。不同的存储引擎具有不同的功能,这样我们可以根据自己的需要,来选取合适的存储引擎。
4)存储层:数据存储层, 主要是将数据存储在文件系统之上,并完成与存储引擎的交互。
和其他数据库相比,MySQL有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用。主要体现在存储引擎上,插件式的存储引擎架构,将查询处理和其他的系统任务以及数据的存储提取分离。这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
如何查看mysql提供的所有存储引擎
mysql> show engines;
mysql常用引擎包括:MYISAM、Innodb、Memory、MERGE
存储引擎就是存储数据,建立索引,更新查询数据等等技术的实现方式 。存储引擎是基于表的,而不是基于库的。所以存储引擎也可被称为表类型。
在 MySQL 5.1 及之前的版本中,MyISAM 是默认的存储引擎,而在 MySQL 5.5 版本以后,默认使用 InnoDB 存储引擎。
MEMORY 与 MERGE
事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,它是逻辑上的一组操作,要么都执行,要么都不执行。
事务及其ACID属性
事务是由一组SQL语句组成的逻辑处理单元。
事务具有以下4个特性,简称为事务ACID属性。
ACID属性 | 含义 |
---|---|
原子性(Atomicity) | 事务是一个原子操作单元,其对数据的修改,要么全部成功,要么全部失败。 |
一致性(Consistent) | 在事务开始和完成时,数据都必须保持一致状态。 |
隔离性(Isolation) | 数据库系统提供一定的隔离机制,保证事务在不受外部并发操作影响的 “独立” 环境下运行。 |
持久性(Durable) | 事务完成之后,对于数据的修改是永久的。 |
问题 | 含义 |
---|---|
丢失更新(Lost Update) | 当两个或多个事务选择同一行,最初的事务修改的值,会被后面的事务修改的值覆盖。 |
脏读(Dirty Reads) | 当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。 |
不可重复读(Non-Repeatable Reads) | 一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现和以前读出的数据不一致。 |
幻读(Phantom Reads) | 一个事务按照相同的查询条件重新读取以前查询过的数据,却发现其他事务插入了满足其查询条件的新数据。 |
数据库的隔离级别有4个,由低到高依次为Read uncommitted、Read committed、Repeatable read(默认)、Serializable,这四个级别可以逐个解决脏写、脏读、不可重复读、幻读这几类问题。
隔离级别并不是越高越好,需要进行权衡
隔离级别 | 丢失更新 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|---|
Read uncommitted | × | √ | √ | √ |
Read committed | × | × | √ | √ |
Repeatable read(默认) | × | × | × | √ |
Serializable | × | × | × | × |
备注 : √ 代表可能出现 , × 代表不会出现 。
Repeatable read(默认):明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。
在事物中存在以下几种隔离级别:
读未提交(Read Uncommitted)
:解决更新丢失问题。如果一个事务已经开始写操作,那么其他事务则不允许同时进行写操作,但允许其他事务读此行数据
。该隔离级别可以通过“排他写锁”实现,即事物需要对某些数据进行修改必须对这些数据加 X 锁,读数据不需要加 S 锁。
读已提交(Read Committed)
:解决了脏读问题。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。这可以通过“瞬间共享读锁”和“排他写锁”实现, 即事物需要对某些数据进行修改必须对这些数据加 X 锁,读数据时需要加上 S 锁,当数据读取完成后立刻释放 S 锁,不用等到事物结束。
可重复读取(Repeatable Read)
:禁止不可重复读取和脏读取,但是有时可能出现幻读数据。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。Mysql默认使用该隔离级别。这可以通过“共享读锁”和“排他写锁”实现,即事物需要对某些数据进行修改必须对这些数据加 X 锁,读数据时需要加上 S 锁,当数据读取完成并不立刻释放 S 锁,而是等到事物结束后再释放。
串行化(Serializable)
:解决了幻读的问题的。提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
事务是基于重做日志(redo log)和回滚日志(undo log)实现的,这两个就是所说的事务日志。
InnoDB存储引擎是以页为单位来管理存储空间的。在访问真正的页面之前,需要把在磁盘上的页缓存到内存中的Buffer Pool之后才可以访问。所有的数据变更也都必须新更新缓冲池中的数据,然后缓冲池中的脏页会以一定的频率被刷入磁盘(checkPoint机制),通过缓冲池来优化CPU和磁盘之间的鸿沟,这样就可以保证整体的性能不会下降太快。
redo log 用来实现事务的持久性,即事务 ACID 中的 D。其由两部分组成:
重做缓冲日志(redo log buffer)
在服务器启动时就向操作系统申请了一大片称为redo log buffer的连续内存空间,即redo日志缓冲区。这片内存空间被划分为若干个连续的redo log block。每个redo log block占用 512字节 大小。
参数 innodb_log_buffer_size 可以来设置 redo log buffer的大小
redo log buffer 大小,默认16M ,最大值是4096M,最小值为1M。
mysql> show variables like '%innodb_log_buffer_size%';
+------------------------+----------+
| Variable_name | Value |
+------------------------+----------+
| innodb_log_buffer_size | 16777216 |
+------------------------+----------+
第二步操作是在事务的执行过程中就开始执行了
,而不是等到commit时才执行);追加写
的方式;undo log: undo log 用来提供回滚和多个行版本控制(MVCC,快照度)。事务未提交之前,Undo 保存了未提交之前的版本数据。是为了实现事务的 原子性
而出现的产物。
redo log的写入并不是直接写入磁盘的,InnoDB引擎会在写redo log的时候先写redo log buffer,之后以一定的频率刷入到真正的redo log file 中。
redo log buffer刷盘到redo log file的过程并不是真正的刷到磁盘中去,只是刷入到文件系统缓存(page cache)中去(这是现代操作系统为了提高文件写入效率做的一个优化),真正的写入会交给系统自己来决定(比如page cache足够大了)。
那么对于InnoDB来说就存在一个问题,如果交给系统来同步,同样如果系统宕机,那么数据也丢失了(虽然整个系统宕机的概率还是比较小的)。
针对这种情况,InnoDB给出innodb_flush_log_at_trx_commit 参数,该参数控制 commit提交事务时,如何将 redo log buffer 中的日志刷新到 redo log file 中。它支持三种策略:
补充:InnoDB存储引擎有一个后台线程(master thread),每隔 1 秒,就会把 redo log buffer 中的内容写到文件系统缓存(page cache),然后调用刷盘策略(fsync)。所以,针对设置为0的情况,就是由后台线程进行刷盘。
undo log是事务原子性的保证。在事务中更新数据的前置操作其实是要先写入一个undo log 。
MySQL把这些为了回滚而记录的这些内容(insert、delete、update)称之为撤销日志或者回滚日志(即undo log)。注意,由于 查询操作SELECT
并不会修改任何用户记录,所以在查询操作执行时,并不需要记录相应的undo日志。
此外,undo log会产生redo log,也就是undo log的产生会伴随着redo log的产生,这是因为undo log也需要持久性的保护。
作用1:回滚数据
用户对undo日志可能有误解:undo用于将数据库物理地恢复到执行语句或事务之前的样子,但事实并非如此。undo是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。所有修改都被逻辑地取消了,但是数据结构和页本身在回滚之后可能大不相同。
这是因为在多用户并发系统中,可能会有数十、数百甚至数千个并发事务。数据库的主要任务就是协调对数据记录的并发访问。比如,一个事务在修改当前一个页中某几条记录,同时还有别的事务在对同一个页中另几条记录进行修改。因此,不能将一个页回滚到事务开始的样子,因为这样会影响其他事务正在进行的工作。
作用2:MVCC
undo的另一个作用是MVCC,即在innoDB存储引擎中MVCC的实现是通过undo来完成。当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可以通过undo log读取之前的行版本信息,以此实现非锁定读取。
在lnnoDB存储引擎中,undo log分为:
故该undo log可以在事务提交后直接删除。不需要进行purge操作。
delete
和 update
操作产生的undo log。该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。
对于InnoDB引擎来说,每个行记录除了记录本身的数据之外,还有几个隐藏的列:
当我们执行INSERT时:
begin;
INSERT INTO user (name) VALUES ("tom");
插入的数据都会生成一条 insert undo log,并且数据的回滚指针会指向它。undo log会记录undo log的序号、插入主键的列和值…,那么在进行rollback的时候,通过主键直接把对应的数据删除即可。
当我们执行UPDATE时:
对于更新的操作会产生 update undo log,并且会分 更新主键的和 不更新主键的,假设现在执行执行不更新主键的 UPDATE 操作
UPDATE user SET name="Sun" WHERE id=1;
这时会把老的记录写入新的undo log,让回滚指针指向新的undo log,它的undo no是1,并且新的undo log会指向老的undo log (undo no=0)。
当我们执行更新主键的 UPDATE 操作时
UPDATE user SET id=2 WHERE id=1;
对于更新主键的操作,会先把原来的数据deletemark标识打开,这时并没有真正的删除数据,真正的删除会交给清理线程去判断,然后在后面插入一条新的数据,新的数据也会产生undo log,并且undo log的序号会递增。
可以发现每次对数据的变更都会产生一个undo log,当一条记录被变更多次时,那么就会产生多条undo log
,undo log记录的是变更前的日志,并且 每个undo log的序号是递增的
,那么当要回滚的时候,按照序号依次向前推
,就可以找到我们的原始数据了。
针对于insert undo log
在事务提交后直接删除,不需要进行purge操作
。针对于update undo log
MVCC机制
,因此 不能在事务提交时就进行删除
。提交时放入undo log链表,等待purge线程进行最后的删除
。purge线程两个主要作用是:清理undo页 和 清除page里面带有Delete_Bit标识的数据行。
在InnoDB中,事务中的Delete操作实际上并不是真正的删除掉数据行,而是一种Delete Mark操作,在记录上标识Delete_Bit,而不删除记录。是一种"假删除",只是做了个标记,真正的删除工作需要后台purge线程去完成。
尽量不要在同一个事务中使用多种存储引擎,MySQL服务器层不管理事务,事务是由下层的存储引擎实现的。
如果在事务中混合使用了事务型和非事务型的表(例如InnoDB和MyISAM表), 在正常提交的情况下不会有什么问题。但如果该事务需要回滚,非事务型的表上的变更就无法撤销,这会导致数据库处于不一致的状态
,这种情况很难修复,事务的最终结果将无法确定。
读未提交和串行化
基本上是不需要考虑的隔离级别,前者不加锁限制,后者相当于单线程执行,效率太差。
MySQL 在 可重复读
级别 解决了幻读问题
,是 通过行锁和间隙锁的组合 Next-Key 锁实现的
。
MVCC (Multiversion Concurrency Control)
,多版本并发控制。顾名思义,MVCC 是通过数据行的 多个版本
管理来实现数据库的 并发控制
。这项技术使得在InnoDB的事务隔离级别下执行 一致性读操
作有了保证。换言之,就是为了查询一些正在被另一个事务更新的行,并且可以看到它们被更新之前的值,这样在做查询的时候就不用等待另一个事务释放锁。
快照读
快照读又叫一致性读,读取的是快照数据。不加锁的简单的 SELECT 都属于快照读,即不加锁的非阻塞读;比如这样:
SELECT * FROM player WHERE ...
之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于MVCC,它在很多情况下,避免了加锁操作,降低了开销。
既然是基于多版本,那么快照读可能读到的 并不一定是数据的最新版本
,而 有可能是之前的历史版本
。快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读。
当前读
当前读 读取的是记录的 最新版本
(最新数据,而不是历史版本的数据),读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。加锁的 SELECT,或者对数据进行增删改都会进行当前读。比如:
SELECT * FROM student LOCK IN SHARE MODE; # 共享锁
SELECT * FROM student FOR UPDATE; # 排他锁
INSERT INTO student values ... # 排他锁
DELETE FROM student WHERE ... # 排他锁
UPDATE student SET ... # 排他锁
MVCC 的实现依赖于:隐藏字段、Undo Log、Read View。
对于 InnoDB ,聚簇索引记录中包含 3 个隐藏的列:
DB_ROW_ID(隐藏的自增 ID)
:如果表没有主键,InnoDB 会自动按 ROW ID 产生一个聚集索引树;DB_TRX_ID(事务ID)
:记录最近更新这条行记录的事务 ID,大小为 6 个字节;DB_ROLL_PTR(回滚指针)
:表示指向该行回滚段(rollback segment)的指针,大小为 7 个字节。该行记录上所有旧版本,在 undo 中都通过链表的形式组织。在MVCC机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在Undo Log里。如果一个事务想要查询这个行记录,需要读取哪个版本的行记录呢?这时就需要用到ReadView了,它帮我们解决了行的可见性问题。
ReadView就是事务在使用MVCC机制进行快照读操作时产生的读视图。当事务启动时,会生成数据库系统当前的一个快照,InnoDB为每个事务构造了一个数组,用来记录并维护系统当前活跃事务的ID(“活跃"指的就是,启动了但还没提交)。
使用 READ UNCOMMITTED
隔离级别的事务,由于可以读到未提交事务修改过的记录,所以直接读取记录的最新版本就好了。
使用 SERIALIZABLE
隔离级别的事务,InnoDB规定使用加锁的方式来访问记录。
使用 READ COMMITTED
和 REPEATABLE READ
隔离级别的事务,都必须保证读到已经提交了的事务修改过的记录
。假如另一个事务已经修改了记录但是尚未提交,是不能直接读取最新版本的记录的,核心问题就是需要判断一下版本链中的哪个版本是当前事务可见的,这是ReadView要解决的主要问题。
这个ReadView中主要包含4个比较重要的内容,分别如下:
做改动
时(执行INSERT、DELETE、UPDATE这些语句时)才会为事务分配事务id,否则在一个只读事务中的事务id值都 默认为0。活跃的
读写事务的事务id列表(“活跃"指的就是,启动了但还没提交)。活跃的
事务中 最小的事务 ID
。需要区别于正在活跃的事务ID
。有了这个ReadView,这样在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见。
trx_id
属性值与 ReadView 中的 creator_trx_id
值 相同
,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。trx_id
属性值 小于
ReadView 中的 up_limit_id
值,表明生成该版本的事务在当前事务生成ReadView前 已经提交
,所以该版本可以被当前事务访问。trx_id
属性值 大于或等于
ReadView中的 low_limit_id
值,表明生成该版本的事务在当前事务生成ReadView后才开启,所以该版本 不可以
被当前事务访问。trx_id
属性值在 ReadView 的 up_limit_id
和 low_limit_id
之间
,那就需要判断一下trx_id属性值是不是在trx_ids 列表中。
了解了这些概念之后,我们来看下当查询一条记录的时候,系统如何通过MVCC找到它:
获取事务自己的版本号,也就是事务 ID
;如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照
;返回符合规则的数据
。如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本。如果最后一个版本也不可见的话,那么就意味着该条记录对该事务完全不可见,查询结果就不包含该记录
。
InnoDB中,MVCC是通过Undo Log + Read View进行数据读取,Undo Log保存了历史快照,而Read View规则帮我们判断当前版本的数据是否可见。
在隔离级别为读已提交(Read Committed)时,一个事务中的每一次 SELECT 查询都会重新获取一次 Read View。
注意:此时同样的查询语句都会重新获取一次 Read View,这时如果 Read View 不同,就可能产生不可重复读或者幻读的情况。。
当隔离级别为可重复读(REPEATABLE READ)的时候,就避免了不可重复读,这是因为一个事务只在第一次SELECT 的时候会获取一次 Read View,而后面所有的 SELECT 都会复用这个 Read View。
MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理 读-写冲突
,做到即使有读写冲突时,也能做到不加锁, 非阻塞并发读,而这个读指的就是 快照读
,而非当前读。当前读
实际上是一种加锁的操作,是悲观锁的实现。而MVCC本质是采用乐观锁思想的一种方式。
我看到有的博客说MVCC在REPEATABLE READ级别下解决了幻读,这么说是不准确的,下面我们分析一下只依靠MVCC到底能不能能解决幻读。先说结论:只依靠MVCC不能解决幻读,MVCC是为了解决 读-写 之间阻塞的问题(排他锁会阻塞读操作),写操作还是需要加锁(Next-Key Lock)。如果没有MVCC,那么修改数据的操作会加排它锁,其它的读写操作都会阻塞,这样的话效率会比较低。
在之前的文章MySQL 锁:InnoDB引擎中锁分类以及表锁、行锁、页锁详解中介绍过,解决脏读、不可重复读、幻读这些问题有两种可选的解决方案:
- 方案一:读操作 利用多版本并发控制( MVCC),写操作 进行加锁。(重要 重要)
- 方案二:读、写操作都采用加锁的方式。
方案一分析:
- 读操作使用 MVCC ,指的是 快照读;
- 写操作 进行加锁,指的是 当前读。
方案二分析:读、写操作都采用加锁的方式,指的都是 当前读
结论:在REPEATABLE READ级别下只依靠MVCC本身是不能解决幻读的,MVCC解决的是读操作,实现了读-写不冲突,写操作依旧需要加锁(Next-Key Lock)。下面我们进行详细分析
这里介绍了 MVCC
在 READ COMMITTD
、REPEATABLE READ
这两种隔离级别的事务在执行快照读操作时访问记录的版本链的过程。这样使 不同事务的读-写、写-读操作并发执行
,从而提升系统性能。
核心点在于 ReadView 的原理
, READ COMMITTD 、REPEATABLE READ 这两个隔离级别的一个很大不同就是生成ReadView的时机不同:
READ COMMITTD
在 每一次
进行 普通SELECT
操作前都会生成一个 ReadView
;REPEATABLE READ
只在第一次
进行 普通SELECT
操作前生成一个 ReadView
,之后的查询操作都重复使用这个ReadView就好了
。说明:我们之前说执行DELETE语句或者更新主键的UPDATE语句并不会立即把对应的记录完全从页面中删除,而是执行一个所谓的delete mark操作,相当于只是对记录打上了一个删除标志位,这主要就是为MVCC服务的。
在关系型数据库中,可以按照锁的粒度把数据库锁分为 行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )。
行级锁
会出现死锁
;共享锁 和 排他锁。
表级锁
表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。
不会出现死锁
;锁定粒度大,发出锁冲突的概率最高,并发度最低。页级锁
会出现死锁
;锁定粒度界于表锁和行锁之间,并发度一般MyISAM和InnoDB存储引擎使用的锁:
表级锁更适合于以 查询为主,只有少量按索引条件更新数据的应用
,如Web 应用;
而行级锁则更适合于有大量按索引条件并发更新少量不同数据,同时又有并查询的应用,如一些在线事务处理(OLTP)系统。
版本号机制或CAS算法实现。
写比较少
的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。死锁是指 两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环
的现象。常见的解决死锁的方法:
1、尽量约定以相同的顺序访问表;
2、在同一个事务中,尽可能做到一次锁定所需要的所有资源;
3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度
,通过表级锁定来减少死锁产生的概率;
4、如果业务处理不好 可以用分布式事务锁或者使用乐观锁
。
尽可能减少索引条件,及索引范围,避免间隙锁
MyISAM 存储引擎只支持表锁
自动给涉及的所有表加读锁
自动给涉及的表加写锁
显示加表锁语法(lock table table_name read(or write)
):
加读锁 : lock table table_name read;
加写锁 : lock table table_name write;
1) 读锁不会会阻塞其它事务的读请求,但会阻塞对同一表的写请求;
2) 写锁会阻塞其他用户对同一表的读和写操作;
MyISAM 的读写锁调度是写优先,这也是MyISAM不适合做写为主的表的存储引擎的原因。因为写锁后,其他线程不能做任何操作,大量的更新会使查询很难得到锁,从而造成永远阻塞。
InnoDB 与 MyISAM 的最大不同有两点:一是支持事务;二是 采用了行级锁。
InnoDB 实现了以下两种类型的行锁。
只能读不能修改
。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及数据集加排他锁(X)
;
对于普通SELECT语句,InnoDB不会加任何锁
;
可以通过以下语句显示给记录集加共享锁或排他锁 。
共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他锁(X) :SELECT * FROM table_name WHERE ... FOR UPDATE
如果不通过索引条件检索数据,那么InnoDB将对表中的所有记录加锁,实际效果跟表锁一样。
InnoDB除了支持行锁(Record Lock,对索引记录加锁)外还支持间隙锁(Gap Lock)、next-key lock
间隙锁的目的是为了防止幻读,其主要通过两个方面实现这个目的:
防止间隙内有新数据被插入
防止已存在的数据,更新成间隙内的数据
innodb自动使用间隙锁的条件:
Repeatable Read
级别下检索条件必须有普通索引
(没有索引的话,mysql会全表扫描,那样会锁定整张表所有的记录,包括不存在的记录,此时其他事务不能修改不能删除不能添加)这里的普通索引 不包括主键索引和唯一索引
,如果在这两个索引下因为能精确检索出结果,所以会使用Record Lock直接锁定具体的行(范围查询除外)。
总结:在可重复读隔离级别下并不能避免幻读,如果要避免的话需要使用Next-Key Lock。但是有了Next-Key Lock以后,会导致并发插入的时候产生等待,所以这时候需要进行相关的优化。
InnoDB 行锁争用情况:show status like 'innodb_row_lock%';
Innodb_row_lock_waits: 系统启动后到现在总共等待的次数(重点关注
)。当等待的次数很高,而且每次等待的时长也不小的时候,我们就需要分析系统中为什么会有如此多的等待,然后根据分析结果着手制定优化计划。
5.6.6 版本之前默认值为 50 , 之后的版本默认为 50 + (max_connections / 5), 但最大不超过900。
如果需要数据库在较短的时间内处理大量连接请求, 可以考虑适当增大back_log 的值。可打开表缓存的数量
( 针对的是所有线程
)MySQL 会缓存一定数量的客户服务线程以备重用
,默认是9。等待行锁的时间
,默认值是50ms。不包括数据查询语句
。此日志对于灾难时的数据恢复起着极其重要的作用,MySQL的主从复制, 就是通过该binlog实现的。超过
参数 long_query_time 设置值并且扫描记录数**不小于
** min_examined_row_limit
的所有的SQL语句的日志。long_query_time 默认为 10 秒,最小为 0, 精度可以到微秒。min_examined_row_limit参数表示SQL执行过程中扫描的行数阈值,如果扫描的行数小于该参数设置的值,即使SQL执行耗时大于慢查询阈值,也不会记录到慢查询日志中,该参数默认值为0。MySQL的 binlog 是记录所有DDL和DML语句, 不会记录 SELECT 和 SHOW 这类操作。
MySQL binlog 以事件形式记录,还包含语句所执行的消耗的时间
,MySQL 的二进制日志是事务安全型的。binlog 的主要目的是复制和恢复。
binlog 有三种格式,各有优缺点:
记录的都是SQL语句
,每一条对数据进行修改的SQL都会记录在日志文件中。记录的是行的变化
,很安全。但是 binlog 会比其他两种模式大很多,在一些大表中清除大量数据时在 binlog 中会生成很多条语句,可能导致从库延迟变大。默认
的日志格式,即混合了STATEMENT 和 ROW两种格式。 默认情况下采用STATEMENT,但是在一些特殊情况下采用ROW来进行记录。
需要手动开启,配置文件位置 : /usr/my.cnf
#配置开启binlog日志, 日志的文件前缀为 mysqlbin -----> 生成的文件名如 : mysqlbin.000001,mysqlbin.000002
log_bin=mysqlbin
#配置二进制日志的格式
binlog_format=STATEMENT
查看是否开启: show variables like '%log_bin%'
复制是指将主数据库的DDL 和 DML 操作通过二进制日志传到从库服务器中,然后在从库上对这些日志重新执行(也叫重做),从而使得从库和主库的数据保持同步。
MySQL支持一台主库同时向多台从库进行复制, 从库同时也可以作为其他从服务器的主库,实现链状复制。
实时灾备,用于故障切换
读写分离,提供查询服务
备份,避免影响业务
主从部署必要条件:
主库开启binlog日志(设置log-bin参数)
主从server-id不同
从库服务器能连通主库
从上层来看,复制分成三步:
MySQL的主从复制模式包括异步复制,全同步复制,半同步复制。MySQL默认为异步模式。
mysql主从复制存在的问题:
解决方法:
半同步复制 ==》解决数据丢失的问题
并行复制 ==》解决从库复制延迟的问题,mysql5.7之后。
并行复制,指的是从库开启多个线程,并行读取 relay log 中不同库的日志,然后并行重放不同库的日志,这是库级别的并行。
在master 的配置文件(/usr/my.cnf)中,配置如下内容:服务id、binlog存储路径和文件名、是否只读、需要忽略的数据库
#mysql 服务ID,保证整个集群环境中唯一
server-id=1
#mysql binlog 日志的存储路径和文件名
log-bin=/var/lib/mysql/mysqlbin
#是否只读,1 代表只读, 0 代表读写
read-only=0
#忽略的数据, 指不需要同步的数据库
binlog-ignore-db=mysql
#指定同步的数据库
#binlog-do-db=db01
在 slave 端配置文件中,配置如下内容:
#mysql服务端ID,唯一
server-id=2
#指定binlog日志
log-bin=/var/lib/mysql/mysqlbin
重启后,slave执行如下命令,指定当前从库对应的主库的IP地址,用户名,密码,从哪个日志文件开始的那个位置开始同步推送日志。
change master to master_host= '139.224.80.141', master_user='lish', master_password='lish', master_log_file='mysqlbin.000003', master_log_pos=154;
参考 7.4
在讲解两阶段提交之前,需要对MySQL中的三种日志即binlog、redo log与undo log有一定的了解。
先说结论:在于崩溃恢复。
MySQL为了提升性能,引入了BufferPool缓冲池。查询数据时,先从BufferPool中查询,查询不到则从磁盘加载在BufferPool。
每次对数据的更新,也不总是实时刷新到磁盘,而是先同步到BufferPool中,涉及到的数据页就会变成脏页。同时会启动后台线程,异步地将脏页刷新到磁盘中,来完成BufferPool与磁盘的数据同步。如果在某个时间,MySQL突然崩溃,则内存中的BufferPool就会丢失,剩余未同步的数据就会直接消失。
虽然在更新BufferPool后,也写入了binlog中,但binlog并不具备crash-safe的能力。因为崩溃可能发生在写binlog后,刷脏前。在主从同步的情况下,从节点会拿到多出来的一条binlog。
所以server层的binlog是不支持崩溃恢复的,只是支持误删数据恢复。InnoDB考虑到这一点,自己实现了redo log。
如果只写一次redo log会有什么样的问题呢?
先写binlog,再写redo log
先写redo log,再写binlog
首先比较重要的一点是,在写入redo log时,会顺便记录XID,即当前事务id。在写入binlog时,也会写入XID。
如果在写入redo log之前崩溃,那么此时redo log与binlog中都没有,是一致的情况,崩溃也无所谓。
如果在写入redo log prepare阶段后立马崩溃,之后会在崩恢复时,由于redo log没有被标记为commit。于是拿着redo log中的XID去binlog中查找,此时肯定是找不到的,那么执行回滚操作。
如果在写入binlog后立马崩溃,在恢复时,由redo log中的XID可以找到对应的binlog,这个时候直接提交即可。
总的来说,在崩溃恢复后,只要redo log不是处于commit阶段,那么就拿着redo log中的XID去binlog中寻找,找得到就提交,否则就回滚。在这样的机制下,两阶段提交能在崩溃恢复时,能够对提交中断的事务进行补偿,来确保redo log与binlog的数据一致性。
会将较少的访问频率很高的字段放到一个表里去,然后将较多的访问频率很低的字段放到另外一个表里去
。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。两种分库分表的方式:
range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部分的请求,都是访问最新的数据。实际生产用 range,要看场景。
hash 分发,好处在于说,可以平均分配每个库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的过程,之前的数据需要重新计算 hash 值重新分配到不同的库或表
一般有3个思考方向:
具体说说:
1、查看SQL执行频率
show status like 'Com_______';
show status like 'Innodb_rows_%';
2、定位低效率执行SQL
查看当前MySQL在进行的线程
,包括线程的状态、是否锁表等,可以实时地查看 SQL 的执行情况。3、explain分析执行计划
explain select * from tb_item where id = 1;
id | select查询的序列号,是一组数字,表示的是查询中执行select子句或者是操作表的顺序。 |
---|---|
select_type | 表示 SELECT 的类型,常见的取值有 SIMPLE(简单表,即不使用表连接或者子查询)、PRIMARY(主查询,即外层的查询)、UNION(UNION 中的第二个或者后面的查询语句)、SUBQUERY(子查询中的第一个 SELECT)等 |
table | 输出结果集的表 |
type | 表示表的连接类型,性能由好到差的连接类型为( system —> const -----> eq_ref ------> ref -------> ref_or_null----> index_merge —> index_subquery -----> range -----> index ------> all ) |
possible_keys | 表示查询时,可能使用的索引 |
key | 表示实际使用的索引 |
key_len | 索引字段的长度 |
rows | 扫描行的数量 |
extra | 执行情况的说明和描述 |
show profiles 指令, 来查看SQL语句执行的耗时
。通过show profile for query query_id 语句可以查看到该SQL执行过程中每个线程的状态和消耗的时间。主键顺序插入
,因为InnoDB类型的表是按照主键的顺序保存的,所以将导入的数据按照主键的顺序排列,可以有效的提高导入数据的效率。如果InnoDB表没有主键,那么系统会自动默认创建一个内部列作为主键,所以如果可以给表创建一个主键,将可以利用这点,来提高导入数据的效率。关闭唯一性校验
。手动提交事务
,如果应用使用自动提交的方式,建议在导入前执行 SET AUTOCOMMIT=0,关闭自动提交,导入结束后再执行 SET AUTOCOMMIT=1,打开自动提交,也可以提高导入的效率。子查询是可以被更高效的多表连接查询(JOIN)替代。
Order by 排序优化详解
filesort 排序
(对返回数据进行排序), 不是通过索引直接返回排序结果的排序都叫 FileSort 排序
。覆盖索引
,这种情况即为 using index,不需要额外排序,操作效率高。排序字段
和行指针信息;比较
系统变量 max_length_for_sort_data 的大小和Query语句取出的字段总大小
, 来判定是否那种排序算法,如果max_length_for_sort_data 更大,那么使用第二种优化之后的算法;否则使用第一种
。优化分页查询
一般分页查询时,通过创建覆盖索引能够比较好地提高性能。
一个常见又非常头疼的问题就是 limit 2000000,10 ,此时需要MySQL排序前2000010 记录,仅仅返回2000000 - 2000010 的记录,其他记录丢弃,查询排序的代价非常大 。
行完全相同的SQL语句的时候
,服务器就会直接从缓存中读取结果,当数据被修改,之前的缓存会失效,修改比较频繁的表不适合做查询缓存
。1) 将尽量多的内存分配给MySQL做缓存,但要给操作系统和其他程序预留足够内存。
2) MyISAM 存储引擎的数据文件读取依赖于操作系统自身的IO缓存,因此,如果有MyISAM表,就要预留更多的内存给操作系统做IO缓存。(需要预留更多的内存给操作系统)
3) 排序区、连接区等缓存是分配给每个数据库会话(session)专用的,其默认值的设置要根据最大连接数合理分配
,如果设置太大,不但浪费资源,而且在并发连接较高时会导致物理内存耗尽。(不能设置过大,要根据最大连接数合理分配)
read_rnd_buffer_size是MySQL读入缓冲区大小
,对于需要做排序的myisam表的查询,如带有order by子句的sql,适当增加 read_rnd_buffer_size 的值,可以改善此类的sql性能。但需要注意的是 read_rnd_buffer_size 是每个session独占的,如果默认值设置太大,就会造成内存浪费。
(如果并发连接数过高就会造成内存浪费)缓存命中率越高
,访问InnoDB表需要的磁盘I/O 就越少,性能也就越高。从而减少对磁盘I/O操作。编写顺序
SELECT DISTINCT <select list> FROM <left_table> <join_type> JOIN <right_table> ON <join_condition> WHERE <where_condition> GROUP BY <group_by_list> HAVING <having_condition> ORDER BY <order_by_condition> LIMIT <limit_params>
注意:
匹配2张表中相关联的记录
。除了匹配2张表中相关联的记录外,还会匹配左表中剩余的记录,右表中未匹配到的字段用NULL表示
。使用SELECT子句进行查询(表可以起别名)
SELECT 字段名 FROM 表1,表2 … WHERE 表1.字段 = 表2.字段 AND 其它查询条件
# 示例
SELECT a.id,a.name,a.address,a.date,b.math,b.english,b.chinese
FROM table1 AS b,table2 AS a
WHERE a.id=b.id
注:在上面的的代码中,以两张表的id字段信息相同作为条件建立两表关联,但在实际开发中不应该这样使用,最好用主外键约束来实现。
简单嵌套查询(子查询、内连接)
子查询:子查询是一个SELECT查询,返回单个值且嵌套在SELECT、INSERT、UPDATE和DELETE语句或其它查询语句中,任何可以使用表达式的地方都可以使用子查询。
SELECT id,name,sex,date FROM tb_demo068 WHERE id in(SELECT id FROM tb_demo068 WHERE id='$_POST[test]')
内连接:把查询结果作为WHERE子句的查询条件即称为内连接。
SELECT filedlist FROM table1 [INNER] JOIN table2 ON table1.column1 = table2.column1
其中,filedlist是要显示的字段,INNER表示表之间的连接方式为内连接,table1.column1=table2.column1用于指明两表间的连接条件,如:
SELECT a.name,a.address,a.date,b.chinese,b.math,b.english
FROM table1 AS a INNER JOIN table2 AS b on a.id=b.id
复杂的嵌套查询
多表之间的嵌套查询可以通过谓词IN实现,语法格式如下:
test_expression[NOT] IN{
subquery
}
参数说明:test_expression指SQL表达式,subquery包含某结果集的子查询
多表嵌套查询的原理:无论是多少张表进行嵌套,表与表之间一定存在某种关联,通过WHERE子句建立此种关联实现查询。
多表联合查询
利用SQL语句中的UNION,可以将不同表中符合条件的数据信息显示在同一列中。例:
SELECT * FROM table1 UNION SELECT * FROM table2
使用UNION时应注意以下两点:
复杂内连接查询
复杂的内连接查询是在基本的内连接查询的基础上再附加一些查询条件,如:
SELECT a.name,a.address,a.date,b.chinese,b.math,b.english
FROM table1 AS a INNER JOIN table2 AS b on a.id=b.id
WHERE b.id=(SELECT id FROM table2 WHERE table2.name='$_POST[text]')
总之,实现表与表之间的关联的本质是两表之间存在共同的数据项或者相同的数据项,通过WHERE子句或内连接INNER JOIN … ON语句将两表连接起来,实现查询。
参考:https://m.php.cn/article/488927.html
且三大范式是一级一级依赖的,第二范式建立在第一范式上,第三范式建立第一第二范式上。
DDL(Data Definition Languages)语句:即数据库定义语句,用来创建数据库中的表、索引、视图、存储过程、触发器等,常用的语句关键字有:CREATE,ALTER,DROP,TRUNCATE,COMMENT,RENAME。增删改表的结构
DML(Data Manipulation Language)语句:即数据操纵语句,用来查询、添加、更新、删除等,常用的语句关键字有:SELECT,INSERT,UPDATE,DELETE,MERGE,CALL,EXPLAIN PLAN,LOCK TABLE,包括通用性的增删改查。增删改表的数据
DCL(Data Control Language)语句:即数据控制语句,用于授权/撤销数据库及其字段的权限(DCL is short name of Data Control Language which includes commands such as GRANT and mostly concerned with rights, permissions and other controls of the database system.)。常用的语句关键字有:GRANT,REVOKE。
TCL(Transaction Control Language)语句:事务控制语句,用于控制事务,常用的语句关键字有:COMMIT,ROLLBACK,SAVEPOINT,SET TRANSACTION。
SQL中的drop、delete、truncate都表示删除,但是三者有一些差别:
limit使用说明
SELECT * FROM table LIMIT 5,10;//检索记录行 6-15
SELECT * FROM table LIMIT 95,-1; //检索记录行 96-last
SELECT * FROM table LIMIT 5;//检索前 5 个记录行
分页写法
方式一:select * from table order by id limit m, n
方式二:select * from table where id > #max_id# order by id limit n
语法格式: SELECT TOP n FROM [查询条件],n为要返回结果集中的记录条数。
https://blog.csdn.net/weixin_38192427/article/details/108587776
什么是视图
视图(View)是一种虚拟存在的表。视图并不在数据库中实际存在,行和列数据来自定义视图的查询中使用的表,并且是在使用视图时动态生成的。通俗的讲, 视图就是一条SELECT语句执行后返回的结果集。
所以我们在创建视图的时候,主要的工作就落在创建这条SQL查询语句上。
优点
使用视图的用户只能访问他们被允许查询的结果集,对表的权限管理并不能限制到某个行某个列,但是通过视图就可以简单的实现。
存储过程和函数是 事先经过编译并存储在数据库中的一段 SQL 语句的集合
,调用存储过程和函数可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。
存储过程和函数的区别在于函数必须有返回值,而存储过程没有。
函数 : 是一个有返回值的过程 ;
过程 : 是一个没有返回值的函数 ;
触发器是与表有关的数据库对象,指在 insert/update/delete 之前或之后,触发并执行触发器中定义的SQL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性 , 日志记录 , 数据校验等操作 。
使用别名 OLD 和 NEW (OLD和NEW也称为行记录变量)来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句级触发。
触发器类型 | NEW 和 OLD的使用 |
---|---|
INSERT 型触发器 | NEW 表示将要或者已经新增的数据 |
UPDATE 型触发器 | OLD 表示修改之前的数据 , NEW 表示将要或已经修改后的数据 |
DELETE 型触发器 | OLD 表示将要或者已经删除的数据 |
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。