继续来分享自己的整理的MySQL的知识。本篇博客主要是自己在看 《高性能MySQL》 的过程中做的笔记,其中夹杂着一些对一些知识的深入的解释,这些解释大多是从别人的博客中搬运过来综合整理后得到的,这部分内容的来源会在文中标注出来,建议大家支持原作者,我这里搬运过来主要是为了以后看着方便不用在网页间跳来跳去。
MySQL的逻辑架构分为三层:
SELECT
语句,解析查询前服务器会先检查缓存(要查询语句完全一致,所以命中率不高)。读锁是共享的,互不阻塞,多个用户在同一时刻可以同时读取某个资源而互不干扰。写锁是排他的,写锁会阻塞其他的写锁和读锁。
为了考虑并发性能,需要综合锁的开销和数据的安全性。MySQL有多种锁粒度的选择:
在存储引擎中,会根据隔离级别,自动地进行隐式锁定,但是也可以设置显式锁定(第一行是共享锁,第二行是排它锁):
上面的两个语句是在事务内起作用的,所涉及的概念是行锁。它们能够保证当前session
事务所锁定的行不会被其他session
所修改(这里的修改指更新或者删除)。两个语句不同的是,一个是加了共享锁而另外一个是加了排它锁.
为了防止间隙锁,锁的力度应该尽可能小,选择时要尽可能精细选择。
对数据的修改操作会先直接修改内存中的 Page
,但这些页不会立刻同步磁盘,这时内存中的数据已经和磁盘上的不一致了,我们称这种 Page
为脏页。为了保证数据的安全性,在修改内存中的 Page
之后 InnoDB
会写 redo log
,然后,InnoDB
会在事务提交前将 redo log
保存到磁盘中。这里所说的 redo log
是物理日志而非逻辑日志,记录的是数据页的物理修改,而不是某一行或某几行修改成怎样怎样,它用来恢复提交后的物理数据页(恢复数据页,且只能恢复到最后一次提交的位置)。
与 redo log
不同,undo log
一般是逻辑日志,根据每行记录进行记录。例如当 DELETE
一条记录时,undo log
中会记录一条对应的 INSERT
记录,反之亦然当 UPDTAE
一条记录时,它记录一条对应反向 UPDATE
记录。通过 undo log
一方面可以实现事务回滚,另一方面可以根据 undo log
回溯到某个特定的版本的数据,实现 MVCC
的功能。
redo log
由两部分组成,一部分是内存中的 redo log buffer
,这部分是易失的,重启就没了;二是磁盘上的 redo log file
,是持久化的。
InnoDB
通过 force log at commit
技术来实现事务的持久化特性。为了保证每次 redo log
都能写入磁盘上的日志文件中,每次将内存中的 redo log buffer
内容同步磁盘时都会调用一次 fsync
。
InnoDB在每行数据都增加三个隐藏字段,一个唯一行号,一个记录创建的版本号,一个记录回滚的版本号:
其中:
(rollback segment)
的指针,大小为 7
个字节,InnoDB
便是通过这个指针找到之前版本的数据。该行记录上所有旧版本,在 undo
中都通过链表的形式组织。上文提到,在多个事务并行操作某行数据的情况下,不同事务对该行数据的 UPDATE 会产生多个版本,然后通过回滚指针组织成一条 Undo Log
链。事务 A
对值 x
进行更新之后,该行即产生一个新版本和旧版本。假设之前插入该行的事务 ID
为 100
,事务 A
的 ID
为 200
,该行的隐藏主键为 1
。
事务 A
的操作过程为:
DB_ROW_ID = 1
的这行记录加排他锁undo log
中,DB_TRX_ID
和 DB_ROLL_PTR
都不动DATA_TRX_ID
为修改记录的事务 ID
,将 DATA_ROLL_PTR
指向刚刚拷贝到 undo log
链中的旧版本记录,这样就能通过 DB_ROLL_PTR
找到这条记录的历史版本。如果对同一行记录执行连续的 UPDATE
,Undo Log
会组成一个链表,遍历这个链表可以看到这条记录的变迁redo log
,包括 undo log
中的修改那么 INSERT
和 DELETE
会怎么做呢?其实相比 UPDATE
这二者很简单,INSERT
会产生一条新纪录,它的 DATA_TRX_ID
为当前插入记录的事务 ID
;DELETE
某条记录时可看成是一种特殊的 UPDATE
,其实是软删,真正执行删除操作会在 commit
时,DATA_TRX_ID
则记录下删除该记录的事务 ID
。
在 RU
隔离级别下,直接读取版本的最新记录就 OK,对于 SERIALIZABLE
隔离级别,则是通过加锁互斥来访问数据,因此不需要 MVCC
的帮助。因此 MVCC
运行在 RC
和 RR
这两个隔离级别下,当 InnoDB
隔离级别设置为二者其一时,在 SELECT
数据时就会用到版本链。InnoDB
为了解决这个问题,设计了 ReadView
(可读视图)的概念。ReadView
是事务开启时,当前所有事务的一个集合(理解这句话),这个数据结构中存储了当前ReadView
中最大的ID及最小的ID
在 RR
隔离级别下,每个事务 touch first read
时(本质上就是执行第一个 SELECT
语句时,后续所有的 SELECT
都是复用这个 ReadView
,其它 update
, delete
, insert
语句和一致性读 snapshot
的建立没有关系),会将当前系统中的所有的活跃事务拷贝到一个列表生成ReadView
。
下图中事务 A
第一条 SELECT
语句在事务 B
更新数据前,因此生成的 ReadView
在事务 A
过程中不发生变化,即使事务 B
在事务 A
之前提交,但是事务 A
第二条查询语句依旧无法读到事务 B
的修改。
下图中,事务 A
的第一条 SELECT
语句在事务 B
的修改提交之后,因此可以读到事务 B
的修改。但是注意,如果事务 A
的第一条 SELECT
语句查询时,事务 B
还未提交,那么事务 A
也查不到事务 B
的修改
在 RC
隔离级别下,每个 SELECT
语句开始时,都会重新将当前系统中的所有的活跃事务拷贝到一个列表生成 ReadView
。二者的区别就在于生成 ReadView
的时间点不同,一个是事务之后第一个 SELECT
语句开始、一个是事务中每条 SELECT
语句开始。
ReadView
中是当前活跃的事务 ID
列表,称之为 m_ids
,其中最小值为 up_limit_id
,最大值为 low_limit_id
,事务 ID
是事务开启时 InnoDB
分配的,其大小决定了事务开启的先后顺序,因此我们可以通过 ID
的大小关系来决定版本记录的可见性,具体判断流程如下:
如果被访问版本的 trx_id
小于 m_ids
中的最小值 up_limit_id
,说明生成该版本的事务在 ReadView
生成前就已经提交了,所以该版本可以被当前事务访问。
如果被访问版本的 trx_id
大于 m_ids
列表中的最大值 low_limit_id
,说明生成该版本的事务在生成 ReadView
后才生成,所以该版本不可以被当前事务访问。需要根据 Undo Log
链找到前一个版本,然后根据该版本的 DB_TRX_ID 重新判断可见性。
如果被访问版本的 trx_id
属性值在 m_ids
列表中最大值和最小值之间(包含),那就需要判断一下 trx_id
的值是不是在 m_ids
列表中。如果在,说明创建 ReadView
时生成该版本所属事务还是活跃的,因此该版本不可以被访问,需要查找 Undo Log 链得到上一个版本,然后根据该版本的 DB_TRX_ID
再从头计算一次可见性;如果不在,说明创建 ReadView
时生成该版本的事务已经被提交,该版本可以被访问。
此时经过一系列判断我们已经得到了这条记录相对 ReadView
来说的可见结果。此时,如果这条记录的 delete_flag
为 true
,说明这条记录已被删除,不返回。否则说明此记录可以安全返回给客户端。
良好事务处理系统,必须具备ACID
特征:
指两个或多个事务在同一资源上相互占用,并请求锁定对方占用的资源,谁都不释放,导致恶性循环。解决这种问题的方式,一种是死锁检测,另一个是死锁超时。
数据存储在表空间tablespace中,表空间中又分为Segment、Extent、page
支持事务,通过MVCC来实现乐观锁,通过行锁实现悲观锁,间隙锁防止部分幻读
基于聚簇索引建立,聚簇索引对主键的查询有很高的性能,但是二级索引必须包含主键,查询二级索引后,需要回表去聚簇索引中查
.MYD
)和索引文件(扩展名.MYI
) ,表可存储的行记录数,一般受限于可用的磁盘空间或者操作系统中单个文件的最大尺寸更小的通常更好: 使用可以正确存储数据的最小数据类型
MySQL中的数据类型可以分为:整型(tinyint、smallint、mediumint、bigint、int)、浮点型(double、float、DECIMAL)、字符串(char和varchar)、日期(date、time、datetime、timestamp(自动存储记录修改时间))、二进制几个大类。
简单就好: 尽量用简单的数据类型,能用整型的就不用字符串
尽量避免NULL: 最好设置列值为NOT NULL
,除非真的需要NULL
值
使用枚举代替字符串类型: 枚举可以防止重复的存储相同的字符串
为了提高查询效率,不一定所有的表要满足三范式,可以适当混用范式和反范式。最常见的反范式数据的方法是复制或缓存,在不同的表中存储已有的特定列。可以使用触发器,定期更新缓存值
schema
其他表获取数据,但是每次获取的速度比较慢的表Group by
语句聚合数据的表,如要统计网站一天的访问量,可以每个小时统计一次,最后对这些汇总表再统计在遇到计数的需求时,一种解决方案是,建立一个表,里面有一行数据,然后每次都更新这一行数据,但是这种在高并发情况下,很容易导致阻塞,解决方法是,在表中插入固定行数的数据,每次更新时,随机挑去一行更新,要计算总数的时候,计算sum
即可。
1.B-Tree索引
特点:
Order By
,因为索引本身有序限制:
为什么使用B树索引:
2. 哈希索引
基于哈希表实现,只有精确匹配索引所有列的查询才有效,只有Memory
引擎支持哈希索引,其通过拉链法实现哈希表
限制:
自己实现哈希
可以借鉴哈希的思想,在表中生成一列,作为哈希列,通过自定义的哈希函数来计算哈希值。查询时,可以先通过哈希值来过滤。自定义哈希值需要触发器配合来更新哈希。
始终将索引列单独放在比较符号的一侧。 如下面的查询就不能用到索引:
1.是否要为所有的查询建立索引: 不是。一方面,索引要占用额外的存储空间(在InnoDB中,如果主键很长,那索引多了占用的存储空间将非常大)。另一方面,由于索引需要维护,所以过多的索引会造成插入删除修改等操作性能的降低。
2. 不建议使用索引的两种情况:
3. 前缀索引
当索引很长时,可以选择字段开始的部分字符串,节约空间。在选择前缀的长度时,既不能太长,又要保证较高的选择性。有两种做法:
下面情况下,MySQL会进行索引合并
应该遵循一个原则,越前面的索引,应该越能大量的过滤不符合条件的数据
使用聚簇索引时,数据行存放在索引的叶子页。一个表只能有一个聚簇索引,InnoDB通过主键聚集数据。
优点:
①将相关的数据保存在一起,如电子邮件数据库,根据用户ID聚集,就能将同一用户的全部邮件聚集在一起
②数据访问更快
③使用覆盖索引扫描的查询可以直接使用页节点的中的主键值
缺点:
①插入速度严重依赖插入顺序,按照主键的顺序插入是加载数据到InnoDB表中速度最快的方式。如果不是按照主键顺序加载数据,那在加载完数据后最好使用OPTIMIZE TABLE
命令重新组织表
②更新聚簇索引列的代价很高,会强制将每个被更新的行移动到新的位置
③基于聚簇索引的表在插入新行,或者主键被更新导致要移动行的时候,可能导致页分裂,会占用表占用更多的磁盘空间
④如果主键很长,二级索引会非常大。
⑤二级索引访问需要两次索引查找
选择主键时,最好选择一个与业务无关的自增整数作为主键。原因有以下两点:
这样就会形成一个紧凑的索引结构,近似顺序填满。由于每次插入时也不需要移动已有数据,因此效率很高,也不会增加很多开销在维护索引上。
如果使用非自增主键(如果身份证号或学号等),由于每次插入主键的值近似于随机,因此每次新纪录都要被插到现有索引页得中间某个位置:
此时MySQL不得不为了将新记录插到合适位置而移动数据,甚至目标页面可能已经被回写到磁盘上而从缓存中清掉,此时又要从磁盘上读回来,这增加了很多开销,同时频繁的移动、分页操作造成了大量的碎片,得到了不够紧凑的索引结构,*-后续不得不通过OPTIMIZE TABLE来重建表并优化填充页面。因此,只要可以,请尽量在InnoDB上采用自增字段做主键。
可以直接通过索引获取需要查询的数据,而不需要回表,能极大提高性能。有时候,当查询语句无法使用到覆盖索引,可以使用延迟关联的方式,来进行查询。
延迟关联:可以先利用覆盖索引,将符合条件的主键全都查询出来,再统一利用主键去join主表。
只有当索引的顺序和ORDER BY
子句的顺序完全一致,并且所有列的排序方向都一样时,MySQL才能够使用索引来对结果做排序。如果查询需要关联多张表,则只有当ORDER BY
子句引用的字段全部为第一个表时,才能使用索引做排序。ORDER BY
子句和查找型查找的限制是一样的:需要满足索引的最左前缀的要求,否则无法利用索引排序。
使用InnoDB,只有在访问行时才会对其加锁,而索引能够减少InnoDB访问的行数,从而减少行的数量。但是这种情况下,如果索引无法过滤掉无效的行,会导致间隙锁,并且只有在事务提交后才能释放锁。如下面的查询语句:
这条查询仅仅会返回2~4之间的行,但是实际上获取了1-4之间的排它锁,会锁住第一行,因为是通过actor_id<5
来获取,没有排除第一行,所以会锁住所有actor_id
小于5的行。
=
或in
)时,索引可以被用到。其次,由于MySQL的优化,这种情况下,where
中条件的顺序其实可以可以颠倒,MySQL会对这种情况下的条件进行重排,使其和索引顺序一致。(col1,col2,col3)
,查询的where条件是col1='value' and col2='value2'
,这样只能用到col1
的索引,不会用到col3
的索引。>
、<
等范围条件,范围列能用到索引, 但是范围列后面的列无法用到索引。除了上面的一些情况,使用 !
、<>
、is null
、is not null
也会导致索引失效。
MyISAM使用的是B树索引,InnDB使用的是B+树索引,其表的表示方式如下所示:
注意: 在InnoDB中,主键最好是一个int类型的自增数字,因为占用空间小,插入时不需要调整索引结构
建表的时候
create table fulltext_test (
id int(11) NOT NULL AUTO_INCREMENT,
content text NOT NULL,
tag varchar(255),
PRIMARY KEY (id),
FULLTEXT KEY content_tag_fulltext(content,tag) // 创建联合全文索引列
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
通过create
增加
create fulltext index content_tag_fulltext
on fulltext_test(content,tag);
通过alter
增加
alter table fulltext_test
add fulltext index content_tag_fulltext(content,tag);
不同于like
,全文索引有自己的语法,需要用match
和against
关键字
select * from fulltext_test
where match(content,tag) against('xxx xxx');
注意: match() 函数中指定的列必须和全文索引中指定的列完全相同,否则就会报错,无法使用全文索引,这是因为全文索引不会记录关键字来自哪一列。如果想要对某一列使用全文索引,请单独为该列创建全文索引。
MySQL 中的全文索引,有两个变量,最小搜索长度和最大搜索长度,对于长度小于最小搜索长度和大于最大搜索长度的词语,都不会被索引。通俗点就是说,想对一个词语使用全文索引搜索,那么这个词语的长度必须在以上两个变量的区间内。
自然语言搜索引擎的为默认搜索引擎
自然语言搜索引擎将计算每一个文档对象和查询的相关度。这里,相关度是基于匹配的关键词的个数,以及关键词在文档中出现的次数。在整个索引中出现次数越少的词语,匹配时的相关度就越高。相反,非常常见的单词将不会被搜索,如果一个词语的在超过 50% 的记录中都出现了,那么自然语言的搜索将不会搜索这类词语。上面提到的,测试表中必须有 4 条以上的记录,就是这个原因。
这个机制也比较好理解,比如说,一个数据表存储的是一篇篇的文章,文章中的常见词、语气词等等,出现的肯定比较多,搜索这些词语就没什么意义了,需要搜索的是那些文章中有特殊意义的词,这样才能把文章区分开。
在布尔搜索中,我们可以在查询中自定义某个被搜索的词语的相关性,当编写一个布尔搜索查询时,可以通过一些前缀修饰符来定制搜索。
MySQL 内置的修饰符,上面查询最小搜索长度时,搜索结果 ft_boolean_syntax 变量的值就是内置的修饰符,下面简单解释几个,更多修饰符的作用可以查手册
布尔全文索引举例如下:
如果一个列只有有限的几个值(如性别),可以将其放在索引前列用于过滤数据,查询时,加上这个字段的查询即可
经常会用到范围查询的字段,放在索引的后面,防止索引被中断,让查询用到尽可能多的索引列
避免多个范围条件
优化排序:这里涉及到一个limit
优化的问题,主要的优化手段有以下几种:
① (推荐)使用延迟关联的方法,先通过覆盖索引将数据查出来,最后再去查询,举例如下:
select * from table_name inner join ( select id from table_name where (user = xxx) limit 10000,10) b using (id)
②给表增加一个自增索引,直接告诉查询从哪里开始数
在请求数据的时候,只请求需要的数据,主要有以下情况:
select *
,特别是在多表关联时通过explain
中的rows
属性,可大致知道需要扫描的行数,这个数字越小越好。有以下常见的手段:
重构查询方式,有以下策略
1. 用多个简单查询代替复杂查询 :MySQL的连接和断开连接都比较轻量级,所以可以考虑将一个复杂查询拆分成多个简单查询。
2. 切分查询:对于一个大查询,可以分而治之,将其分为几个小查询。如在删除数据时,可能会一下锁住很多数据,但将切分为多个小查询,会极大提升性能。
3. 分解关联查询: 将多个关联查询分解成一个关联查询,有以下好处:
通信是半双工的,同一时间要么是服务器向客户端发送数据,要么是客户端向服务器发送数据,两个动作不能同时发生。
在解析查询语句之前,如果查询缓存打开,那先去查询是否这个查询是否命中缓存。如果命中缓存,会跳过所有其他阶段,直接从缓存中取结果返回给客户端。缓存是否命中的查询,是通过一个对大小写敏感的哈希查找实现的。查询和缓存中的查询,即使只有一个字节不同,也不会匹配缓存结果。
当WHERE
条件中包含IN()
的子查询时,MySQL会将外边的表压入子查询中进行查询。如
会被解析成:
有时MySQL无法将限制条件从外层"下推"到内层,这会使原本能限制部分返回结果的条件无法应用到内存查询的优化上,如:
这里的LIMIT 20
只有在所有数据都查出来以后,才会限制。可以改写成这样:
对于MAX()
和MIN()
查询,MySQL优化的并不好,如下面的查询,会进行一个全表扫描:
可以尝试使用LIMIT
来优化
COUNT: 建议直接使用COUNT(*)
来统计行数
关联查询的优化: 需要做到以下几点:
ON
或者USING
子句中的列上有索引GROUP BY
和ORDER BY
中的表达式只涉及一个表中的列优化GROUP BY:
①优化GROUP BY
:尽量对索引列使用Group BY
②如果不能直接对索引列进行GROUP BY
,那可以先对索引进行GROUP BY
,然后再内连接,例子如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ov2IwzDF-1594904003497)(高性能MySQL.assets/image-20200701201037139.png)]
优化LIMIT分页: 尽可能用覆盖索引的延迟关联,然后再通过关联实现
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7HkW9wCf-1594904003498)(高性能MySQL.assets/image-20200701201237535.png)]
也可以通过在WHERE
中增加限制条件来实现:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bM9PTr3b-1594904003499)(高性能MySQL.assets/image-20200701201427866.png)]
UNION
,因此很多优化策略在UNION
查询中没法很好的使用,经常需要手工地将WHERE、LIMIT、ORDER BY等子句下推到UNION的各个子查询中,以便优化器可以充分利用这些条件优化。Explain查询结果的各字段的意义:
id: 查询的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: type所显示的是查询使用了哪种类型,type包含的类型包括如下图所示的几种:
这些type,从最好到最差依次是:
system > const > eq_ref > ref > range > index > all
一般来说,得保证查询至少达到range级别,最好能达到ref。
system: 表只有一行记录(等于系统表),这是const类型的特列,平时不会出现,这个也可以忽略不计
const: 表示通过索引一次就找到了,const用于比较primary key 或者unique索引。因为只匹配一行数据,所以很快。如将主键置于where列表中,MySQL就能将该查询转换为一个常量。
eq_ref: 唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。常见于主键或唯一索引扫描
ref: 非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以他应该属于查找和扫描的混合体。
range: 只检索给定范围的行,使用一个索引来选择行,key列显示使用了哪个索引,一般就是在你的where语句中出现between、< 、>、in等的查询,这种范围扫描索引比全表扫描要好,因为它只需要开始于索引的某一点,而结束于另一点,不用扫描全部索引。
index: Full Index Scan,Index与All区别为index类型只遍历索引树。这通常比ALL快,因为索引文件通常比数据文件小。(也就是说虽然all和Index都是读全表,但index是从索引中读取的,而all是从硬盘读取的)
All: 全表扫描, 将遍历全表以找到匹配的行
possible_keys 和 key
possible_keys
显示可能应用在这张表中的索引,一个或多个。查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用。覆盖索引
(select 后要查询的字段刚好和创建的索引字段完全相同),则该索引仅出现在key列表中key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度,在不损失精确性的情况下,长度越短越好。key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的。
ref: 显示索引的哪一列被使用了,如果可能的话,最好是一个常数。哪些列或常量被用于查找索引列上的值
rows: 根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数,也就是说,用的越少越好
Extra
总是false
,不能用来获取任何元组分区表就是把一张表分开,对用户来说,分区表是一个独立的逻辑表,但是底层由多个物理子表组成。实现分区的代码实际上是对一组底层表的句柄对象的封装。对分区表的请求,都会通过句柄对象转化成对存储引擎的接口调用。分区表的每一个分区都是有索引的独立表。
(1)表非常大以至于无法全部都放在内存中,或者只在表的最后部分有热点数据,其他均是历史数据。
(2)分区表的数据更容易维护。
(3)分区表的数据可以分布在不同的物理设备上。
(4)可以使用分区表来避免某些特殊的瓶颈,例如InnoDB单个索引的互斥访问。
(5)如果需要,还可以备份和恢复独立的分区,这在非常大的数据集的场景下效果非常好。
①range分区:需要基于连续的范围值
③Hash分区: hash分区指的是根据hash运算的模,最终确定在哪一个分区。比如2020/4=0,就落在分区0上
④线性Hash分区: 线性hash指的是使用2的幂运算法则。运算起来比较麻烦。但是优点是可以使得数据分布均匀。举个例子。假设分区个数num=6,N表示数据最终存储的分区:
第一步:第一步:V = power(2, ceiling(log(2, num))),log是计算NUM以2为底的对数,ceiling()向上取整,power()是取2的次方值;
第二步:N=values&(V-1),&位与运算,
第三步:while N>=num,此时N =N & (CEIL(V/ 2) - 1)
比如插入2020-01-20,V=8,N=(2020)& (8-1)=4。4<6,所以保存在分区4。
需要将by hash
换成by linear hash
⑤根据key分区:根据键值进行分区,以减少InnoDB的互斥竞争
视图本身是一个虚拟表,不存放任何数据,在使用SQL语句访问视图的时候,返回的数据是MySQL从其他表中生成的。与表不同的是,视图不能创建触发器。
在基于视图查询的时候,会有两种方法,一种是合并算法,一种是临时表算法。
InnoDB是唯一支持外键约束的存储引擎,其优缺点如下:
语法: CREATE PROCEDURE 创建的存储过程名字(OUT|IN|INOUT 参数名 数据类型,...,...) 特征 过程体
使用:
示例:
创建存储过程
调用存储过程
和存储过程相比,函数有返回值,且参数类型只能是in
语法: CREATE FUNCTION 创建的存储函数名字(参数名称 参数类型,...,...) RETURNS 返回值得类型 函数体;
使用:
删除存储函数:drop function 函数名字;
示例
语法:
CREATE TRIGGER 创建的触发器名字 BEFFOR|AFTER INSERT|UPDATE|DELETE
ON 表名字 FOR 触发器的执行间隔 触发器的SQL语句
使用:
示例:
创建一个插入型触发器
在表中执行插入的时候,就会触发触发器,然后更新balance
值
事件类似于Linux中的定时任务,可以指定MySQL在某个时候执行一段SQL代码,或者每隔一个时间间隔执行一段SQL代码。通常将复杂的SQL都封装在存储过程中,然后再事件中调用。要使用首先要通过SET GLOBAL event_scheduler=1
来开启定时器。
例子1:每分钟往表中插入一条数据
create event test.event_minute on schedule every 1 minute
do insert into events_list values('event_now', now());
例子2:从2017年的给定时间起,每过一年调用一次存储过程ClearHis(2)
CREATE EVENT `e_ClearHis` ON SCHEDULE EVERY 1 YEAR STARTS '2017-03-07 02:00:00'
ON COMPLETION PRESERVE ENABLE
DO call ClearHis(2);
当创建一个绑定变量 SQL 时,客户端会向服务器发送一个SQL语句的原型。服务器端收到这个SQL语句框架后,解析并存储这个SQL语句的部分执行计划,返回个客户端一个 SQL 语句处理句柄。以后每次执行这类查询,客户端都指定使用这个句柄。绑定变量的SQL,使用问号标记可以接受参数的位置,当真正需要执行具体查询的时候,则使用具体值来替代这些问号。例如,下面是一个绑定变量的SQL语句:
INSERT INTO tb1(col1, col2, col3) VALUES(?,?,?);
主从复制解决的基本问题是让一台服务器的数据与其他服务器保持同步,一台主库的数据可以同步到多台备库上,备库本身也可以配置成为另外一台服务器的主库。主库和备库之间可以有多种不同的组合方式。MySQL有两种复制方式:①基于语句的复制 ②基于行的复制。其基本原理都是,通过在主库上记录二进制日志,在备库中重放日志的方式实现的。
数据分布: 将数据分布在不同服务器
负载均衡: 将读写操作分布到多个服务器上,实现对读密集型应用的优化
备份: 保证数据的安全性
高可用性和故障切换: 主库故障后,从库可以晋升为主库
此模式下,主库会记录那些造成数据库更改的查询,当备库读取并重放这些时间时,实际上是将主库上执行过的SQL再执行一遍。
优点:
缺点:
这种方式将实际数据记录在二进制日志中,然后直接替换行
优点:
缺点:
可以在任意主库和备库之间建立复制,只有一个限制:每个备库只能有一个主库。有以下常见的主从复制的拓扑:
只有一个主库,但有多个备库,多个备库之间没有交互。应用于少量写大量读的场景。有以下用途:
主-主复制包含两台服务器,每一个都被配置成对方的主库和备库。常用于两个不同地理位置的办公室,都需要一份可写的数据拷贝的场景。这种配置最大的问题在于如何解决冲突
如果为每个主库增加一个备库,能够消除单站点失效的问题:
是主-主模式的变体,解决了冲突的问题。其中,被动服务器是只读的。这种拓扑下,反复切换主动和被动服务器非常方便。可以在不关闭服务器的情况下执行维护、优化表、升级操作系统等任务
比如,在执行ALTER TABLE
的时候,可能会锁住整张表,阻止对表的读写,在这种配置下,可以先停止主动服务器的备库复制线程,然后再被动服务器上执行ALTER TABLE
操作,然后交换角色,最后在主动服务器上启动复制线程,更新数据。
环形结构可以有三个或更多的主库,每个服务器都是在它之前的服务器的备库,是在它之后的服务器的主库
备份方式有两种,第一种是逻辑备份,第二种是物理备份。
逻辑备份: 是SQL级别的备份机制,其将数据表导成SQL脚本文件,然后相当于在另一台服务器上执行一遍备份的SQL语句
操作语句: 使用mysqldump
语句来实现,具体语句为:
mysqldump -h主机名 -P端口 -u用户名 -p密码 --database 数据库名 > 文件名.sql
优点: 恢复简单、与存储引擎无关,消除了底层数据存储的不同,有助于避免数据损坏
缺点: 必须有数据库完成逻辑工作,需要更多地CPU周期,且逻辑备份还原慢
物理备份: 是基于文件的物理备份,比较类似于拷贝数据库的文件,然后复制到另一台服务器加载
通过Class.forName("com.mysql.jdbc.Driver");
这句代码,完成了将MySQL的驱动注册到DriverManager中去。由于加载类时,会执行该类的static
块中的内容,所以这句代码接着就会执行static
代码块中的内容对驱动进行注册:
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
public Driver() throws SQLException {
}
static {
try {
//1. 新建一个mysql的driver对象
//2. 将这个对象注册到DriverManager中
DriverManager.registerDriver(new Driver());
} catch (SQLException var1) {
throw new RuntimeException("Can't register driver!");
}
}
}
public static void registerDriver(java.sql.Driver driver) throws SQLException {
//它实际调用了自身的registerDriver方法
registerDriver(driver, null);
}
private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();
public static void registerDriver(java.sql.Driver driver, DriverAction da)
throws SQLException {
/* Register the driver if it has not already been added to our list */
if (driver != null) {
//如果该驱动尚未注册,那么将他添加到 registeredDrivers 中去。这是一个支持并发情况的特殊ArrayList
registeredDrivers.addIfAbsent(new DriverInfo(driver, da));
} else {
// This is for compatibility with the original DriverManager
throw new NullPointerException();
}
println("registerDriver: " + driver);
}
通过下面的语句来获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
其中,getConnection
函数的实现为:
private static Connection getConnection(
String url, java.util.Properties info, Class<?> caller) throws SQLException {
for (DriverInfo aDriver : registeredDrivers) {
Connection con = aDriver.driver.connect(url, info);
}
}
可以看到它对上文提到的特殊ArrayList进行了遍历,调用了connect(url, info); 方法,这是一个接口,由各个不同的驱动自己实现。
执行SQL,先要通过Connection
获取Statement
,再通过Statement
来执行SQL语句。查询结束后,要对Statement
和Connection
关闭。关闭方式有两种,一种是像下面一样,在finally
语句中显式关闭,另一种则是直接用try-with-resource
方式关闭。
public class TestJDBC {
public static void main(String[] args) {
Connection c = null;
Statement s = null;
try {
Class.forName("com.mysql.jdbc.Driver");
c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8", "root",
"admin");
s = c.createStatement();
String sql = "insert into hero values(null," + "'提莫'" + "," + 313.0f + "," + 50 + ")";
s.execute(sql);
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
// 数据库的连接时有限资源,相关操作结束后,养成关闭数据库的好习惯
// 先关闭Statement
if (s != null)
try {
s.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
// 后关闭Connection
if (c != null)
try {
c.close();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
使用PreparedStatement
可以执行预编译的SQL语句。
String sql = "insert into hero values(null,?,?,?)";
try (Connection c = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/how2java?characterEncoding=UTF-8","root", "admin");
// 根据sql语句创建PreparedStatement
PreparedStatement ps = c.prepareStatement(sql);
) {
// 设置参数
ps.setString(1, "提莫");
ps.setFloat(2, 313.0f);
ps.setInt(3, 50);
// 执行
ps.execute();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
优点:
Statement
快,特别是需要多次执行SQL的时候通过ResultSet
来获取结果集,获取时,可以通过列名获取,也可以通过列所在的索引进行获取
ResultSet rs = s.executeQuery(sql);
while (rs.next()) {
int id = rs.getInt("id");// 可以使用字段名
String name = rs.getString(2);// 也可以使用字段的顺序
float hp = rs.getFloat("hp");
int damage = rs.getInt(4);
System.out.printf("%d\t%s\t%f\t%d%n", id, name, hp, damage);
}