高级程序员知识学习(数据库知识点)

高级程序员学习数据库知识点

数据库的三范式

数据库的基本查询语句

数据库的底层数据结构

数据库的索引和优化

数据库的锁

数据库的事务

数据库的复制备份方案

数据库的集群

分布式数据库MongoDB

数据库高级中间件

数据库解决方案


数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建、查询、更新和删除数据。不同的存储引擎提供不同的存储机制、索引技巧、锁定水平等功能,使用不同的存储引擎,还可以 获得特定的功能。现在许多不同的数据库管理系统都支持多种不同的数据引擎。存储引擎主要有:1. MyIsam2.InnoDB3. Memory, 4. Archive, 5. Federated。

什么是关系型数据库,什么是非关系型数据库?

关系型数据库:指采用了关系模型来组织数据的数据库。关系模型指的就是二维表格模型,而一个关系型数据库就是由二维表及其之间的联系所组成的一个数据组织。关系型数据库的最大特点支持事务

缺点:1.网站的用户并发性非常高,硬盘I/O是一个很大的瓶颈。2.海量数据的表中查询,效率是非常低。

非关系型的数据库:指非关系型数据库以键值对存储,且一般不保证遵循ACID原则的数据存储系统。且结构不固定,每一个元组可以有不一样的字段可以减少一些时间和空间的开销。要对多表进行关联查询。仅需要根据id取出相应的value就可以完成查询。

数据库中的视图和表的关系是:

区别:

1、视图是已经编译好的sql语句,而表不是。

2、视图没有实际的物理记录,而表有。

3、表是内容,视图是窗口。

4、表只用物理空间而视图不占用物理空间,视图只是逻辑概念的存在,表可以及时四对它进行修改,但视图只能有创建的语句来修改。

5、表是内模式,试图是外模式。

6、视图是查看数据表的一种方法,可以查询数据表中某些字段构成的数据,只是一些SQL语句的集合。从安全的角度说,视图可以不给用户接触数据表,从而不知道表结构。

7、表属于全局模式中的表,是实表;视图属于局部模式的表,是虚表。

8、视图的建立和删除只影响视图本身,不影响对应的基本表。

9、不能对视图进行update或者insert into操作。

视图(view)是在基本表之上建立的表,它的结构(即所定义的列)和内容(即所有数据行)都来自基本表,它依据基本表存在而存在。一个视图可以对应一个基本表,也可以对应多个基本表。视图是基本表的抽象和在逻辑意义上建立的新关系。

总结:视图是一个子查询,性能肯定会比直接查询要低(尽管sql内部有优化),所以使用视图时有一个必须要注意的,就是不要嵌套使用查询。尤其是复杂查询。

数据库的三范式

1.第一范式:任给关系R,如果R中每个列与行对应单元格的数据都是不可再分的基本元素,则R达到第一范式,简称1NF。(也就是说,不存在组项、向量和重复组,你说向量属于组项,那好吧,那就是说关系R中不存在组项和重复组)

第二范式:1.表必须有主键。2.非主键列必须完全依赖于主键,而不能只依赖主键一部分。

第三范式:非主键列必须直接依赖于主键列,而不存在依赖转移。另外非主键列必须直接依赖于主键,不能存在传递依赖。即不能存在:非主键列 A 依赖于非主键列 B,非主键列 B 依赖于主键的情况。

第二范式(2NF)和第三范式(3NF)的概念很容易混淆,区分它们的关键点在于,2NF:非主键列是否完全依赖于主键,还是依赖于主键的一部分;3NF:非主键列是直接依赖于主键,还是直接依赖于非主键列

数据库的基本查询语句

数据库的名字:Mysql

表明:Student表

UUID

ID

name

age

sex

Chinese

Math

1235dsgasvas12

1

庄小焱1

18

80

94

Dfaf584dfs1a52f1

2

庄小焱2

19

85

80

Fdsaca4f5ds4f1v

3

庄小焱3

20

86

90

1 查询student表中的所有的数据;

Select * from Student;

2 查询出所有名字

    Select all name from Student;

3 过滤掉重复的性别

    Select distinct sex from Student;

4 统计出表中有多少条信息

    Select count(*) from Student;

5 统计出过滤掉重复性别有多少条信息

    Select Count(distinct sex) from Student;

7 两列相运算

    Select (chinese+math) from Student

8 where的语句的查询

    Select id  name age sex from student  where id=2;

    Select id name age sex from student wehere id>2

9 将名字包含庄小焱的信息不显示

    Select * from student where name<>‘庄小焱’

10 and的查询的语句

    Select * from student where id=3 and age =18

11 or 的查询的语句

    Select * frm student where id=2 or age =20

11 between  and

    Select * from student where between age=13 and age=50

12 模糊查询 查询名字中含有庄的所有的信息。

    Select * from student where name like “%庄_”

13 条件子查询in  not in

    Select * from student where id in (10,25,58,78)/ Select * from student where id not in (10,25,58,78)

14 排序查询

    Select * from student order by age

    Select * from student order by age desc(降序排列)

    Select * from student order by age asc(升序排列)

15 group by 分组

select age from [Table] group by age

select id ,name, age from [Table] group by id,name, age

16 按照年龄进行统计进行分组

    Select count(*) age from student group by age /sex

    select COUNT(*) sex from student where id>2 group by sex order by sex 

17 多表的连接的查询

获取两个表的相交的哪一个部分。INNER JOIN(内连接,或等值连接):获取两个表中字段匹配关系的记录

LEFT JOIN(左连接):获取左表所有记录,即使右表没有对应匹配的记录。(左边的数据全部或读取,从而匹配右边的数据)

RIGHT JOIN(右连接): 与 LEFT JOIN 相反,用于获取右表所有记录,即使左表没有对应匹配的记录。

基本数据库的查询语句

功能

SQ语句

 

查询指定的表

select * from 表名

select * from TL_REQUEST

查询指定列

select 列名 from 表名

select BU_NO,BU_NM from TL_REQUEST

条件查询

select * from 表名 where 条件

select * from TL_REQUEST where BU_NM='小芳'

范围查询

 

select * from 表名 where 列名 between 'A' and 'B'

select*from TL_REQUEST where BU_NO between '1000' and '1234'

范围查询

 

select * from 表名 where 列名>='A' and 列名<='B'

select*from TL_REQUEST where BU_NO>='1000' and BU_NO<='1234'

或条件查询:or

select * from 表名 where 列名='A' or列名='B'

select * from TL_REQUEST where BU_NO='1000' or BU_NO='1234'

和条件查询:and

select * from 表名 where 列名='A' and列名='B'

select * from TL_REQUEST where BU_NO='1000' and CONTRACT_NO='tl001'

包含值查询:in()

select * from 表名 where 列名 in('A','B','C')

select * from TL_REQUEST where BU_NO in('1000','1234','1311')

包含值查询:in()

select * from 表名 where 列名='A' 列名='B' 列名='C'

select * from TL_REQUEST where BU_NO='1000' BU_NO='1234' BU_NO='1311'

不包含值查询:not in()

select * from 表名 where 列名 not in('A','B','C')

select * from TL_REQUEST where BU_NO not in('1000','1234','1311')

模糊查询

 

查询列里包含具体中文:select * from 表名 where 列名like '%中文%'

Like:名称前面加。

%:任意多个字符。

_:下划线表示任意一个字符。

示例:select * from TL_REQUEST where BU_NM like '%杜芳%'

或查询第二个字符为芳的情况

select * from TL_REQUEST where BU_NM like '%_芳%'

去重查询

 

select distinct 列名 from 表名

select distinct BU_NO from TL_REQUEST

组合查询

 

select distinct 列名 from 表名 where 条件

select distinct BU_NO from TL_REQUEST where BU_NO between '1000' and '1234'

Mysql中的关键字:(select from where Group by having order by limit)

select * from table t where size > 10 group by size order by size的sql语句执行顺序?表示什么意思:从table中的查询size>10的所有的数据行,这个数据按照分组和并且是按照size的大小顺序升序排序的结果。

order by 用于排序,一般与asc升序或desc降序一起使用。group by 用于度分类汇总,一般与聚合函数(比如avg平均、sum合计、max最大、min最小、count计算版行)一起使用。

select product,sum(price) from orders GROUP BY product HAVING sum(price)>100 ORDER BY sum(price);

mysql中in 和exists 区别。

 

In

Exists(存在,有就返回,没有就null)

区别

in查询相当于多个or条件的叠加,这个比较好理解,比如下面的查询:in查询就是先将子查询条件的记录全都查出来,假设结果集为B,共有m条记录,然后再将子查询条件的结果集分解成m个,再进行m次查询。

exists对外表用loop逐条查询,每次查询都会查看exists的条件语句,当exists里的条件语句能够返回记录行时(无论记录行是的多少,只要能返回),条件就为真,返回当前loop到的这条记录;反之,如果exists里的条件语句不能返回记录行,则当前loop到的这条记录被丢弃,exists的条件就像一个bool条件,当能返回结果集则为true,不能返回结果集则为false

原语句

select * from user where user_id in (1, 2, 3);

select * from user where exists (select 1);

等价于

select * from user where user_id = 1 or user_id = 2 or user_id = 3;

select * from user where exists (select * from user where user_id = 0);

数据库的底层数据结构

索引(Index)是帮助 MySQL 高效获取数据的数据结构。 常见的查询算法,顺序查找,二分查找,二叉排序树查找,哈希散列法,分块查找,平衡多路搜索树 B 树(B-treeB所有树的集合

3.1二叉查找树

二叉树具有以下性质:左子树的键值小于根的键值,右子树的键值大于根的键值。如下图所示就是一棵二叉查找树,

对该二叉树的节点进行查找发现深度为1的节点的查找次数为1,深度为2的查找次数为2,深度为n的节点的查找次数为n,因此其平均查找次数为 (1+2+2+3+3+3) / 6 = 2.3次

二叉查找树可以任意地构造,同样是2,3,5,6,7,8这六个数字,也可以按照下图的方式来构造:

https://img-blog.csdn.net/20160202203448944

但是这棵二叉树的查询效率就低了。因此若想二叉树的查询效率尽可能高,需要这棵二叉树是平衡的,从而引出新的定义——平衡二叉树,或称AVL树。

3.2平衡二叉树(AVL Tree)

平衡二叉树(AVL树)在符合二叉查找树的条件下,还满足任何节点的两个子树的高度最大差为1。下面的两张图片,左边是AVL树,它的任何节点的两个子树的高度差<=1;右边的不是AVL树,其根节点的左子树高度为3,而右子树高度为1;

https://img-blog.csdn.net/20160202203554663

如果在AVL树中进行插入或删除节点,可能导致AVL树失去平衡,这种失去平衡的二叉树可以概括为四种姿态:LL(左左)、RR(右右)、LR(左右)、RL(右左)。它们的示意图如下:

这四种失去平衡的姿态都有各自的定义:

LL:LeftLeft,也称“左左”。插入或删除一个节点后,根节点的左孩子(Left Child)的左孩子(Left Child)还有非空节点,导致根节点的左子树高度比右子树高度高2,AVL树失去平衡。

RR:RightRight,也称“右右”。插入或删除一个节点后,根节点的右孩子(Right Child)的右孩子(Right Child)还有非空节点,导致根节点的右子树高度比左子树高度高2,AVL树失去平衡。

LR:LeftRight,也称“左右”。插入或删除一个节点后,根节点的左孩子(Left Child)的右孩子(Right Child)还有非空节点,导致根节点的左子树高度比右子树高度高2,AVL树失去平衡。

RL:RightLeft,也称“右左”。插入或删除一个节点后,根节点的右孩子(Right Child)的左孩子(Left Child)还有非空节点,导致根节点的右子树高度比左子树高度高2,AVL树失去平衡。

AVL树失去平衡之后,可以通过旋转使其恢复平衡。下面分别介绍四种失去平衡的情况下对应的旋转方法。

LL的旋转。LL失去平衡的情况下,可以通过一次旋转让AVL树恢复平衡。步骤如下:

将根节点的左孩子作为新根节点。

将新根节点的右孩子作为原根节点的左孩子。

将原根节点作为新根节点的右孩子。

RR的旋转:RR失去平衡的情况下,旋转方法与LL旋转对称,步骤如下:

    将根节点的右孩子作为新根节点。

    将新根节点的左孩子作为原根节点的右孩子。

将原根节点作为新根节点的左孩子。

LR的旋转:LR失去平衡的情况下,需要进行两次旋转,步骤如下:

    围绕根节点的左孩子进行RR旋转。

    围绕根节点进行LL旋转。

RL的旋转:RL失去平衡的情况下也需要进行两次旋转,旋转方法与LR旋转对称,步骤如下:

围绕根节点的右孩子进行LL旋转。

围绕根节点进行RR旋转。

3.3B-Tree/B树

B-Tree是为磁盘等外存储设备设计的一种平衡查找树。因此在讲B-Tree之前先了解下磁盘的相关知识。系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。InnoDB存储引擎中默认每个页的大小为16KB,可通过参数innodb_page_size将页的大小设置为4K、8K、16K,在MySQL中可通过如下命令查看页的大小:

一棵m阶的B-Tree有如下特性:

  1. 每个节点最多有m个孩子。
  2. 除了根节点和叶子节点外,其它每个节点至少有Ceil(m/个孩子。
  3. 若根节点不是叶子节点,则至少有2个孩子
  4. 所有叶子节点都在同一层,且不包含其它关键字信息
  5. 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)
  6. 关键字的个数n满足:ceil(m/2)-1 <= n <= m-1
  7. ki(i=1,…n)为关键字,且关键字升序排序。
  8. Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)

B-Tree中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个3阶的B-Tree:

每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为17和35,P1指针指向的子树的数据范围为小于17,P2指针指向的子树的数据范围为17~35,P3指针指向的子树的数据范围为大于35。

模拟查找关键字29的过程:

根据根节点找到磁盘块1,读入内存。【磁盘I/O操作第1次】

比较关键字29在区间(17,35),找到磁盘块1的指针P2。

根据P2指针找到磁盘块3,读入内存。【磁盘I/O操作第2次】

比较关键字29在区间(26,30),找到磁盘块3的指针P2。

根据P2指针找到磁盘块8,读入内存。【磁盘I/O操作第3次】

在磁盘块8中的关键字列表中找到关键字29。

分析上面过程,发现需要3次磁盘I/O操作,和3次内存查找操作。由于内存中的关键字是一个有序表结构,可以利用二分法查找提高效率。而3次磁盘I/O操作是影响整个B-Tree查找效率的决定因素。B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。

3.4B+Tree

B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。

从上一节中的B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。

B+Tree相对于B-Tree有几点不同:

    非叶子节点只存储键值信息。

    所有叶子节点之间都有一个链指针。

    数据记录都存放在叶子节点中。

将上一节中的B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示:

通常在B+Tree上有两个头指针,一个指向根节点,另一个指向关键字最小的叶子节点,而且所有叶子节点(即数据节点)之间是一种链式环结构。因此可以对B+Tree进行两种查找运算:一种是对于主键的范围查找和分页查找,另一种是从根节点开始,进行随机查找。

可能上面例子中只有22条数据记录,看不出B+Tree的优点,下面做一个推算:

InnoDB存储引擎中页的大小为16KB,一般表的主键类型为INT(占用4个字节)或BIGINT(占用8个字节),指针类型也一般为4或8个字节,也就是说一个页(B+Tree中的一个节点)中大概存储16KB/(8B+8B)=1K个键值(因为是估值,为方便计算,这里的K取值为〖10〗^3)。也就是说一个深度为3的B+Tree索引可以维护10^3 * 10^3 * 10^3 = 10亿 条记录。

实际情况中每个节点可能不能填充满,因此在数据库中,B+Tree的高度一般都在2~4层。MySQL的InnoDB存储引擎在设计时是将根节点常驻内存的,也就是说查找某一键值的行记录时最多只需要1~3次磁盘I/O操作。

数据库中的B+Tree索引可以分为聚集索引(clustered index)和辅助索引(secondary index)。上面的B+Tree示例图在数据库中的实现即为聚集索引,聚集索引的B+Tree中的叶子节点存放的是整张表的行记录数据。辅助索引与聚集索引的区别在于辅助索引的叶子节点并不包含行记录的全部数据,而是存储相应行数据的聚集索引键,即主键。当通过辅助索引来查询数据时,InnoDB存储引擎会遍历辅助索引找到主键,然后再通过主键在聚集索引中找到完整的行记录数据。

3.5B*

在B+树的基础上因其初始化的容量变大,使得节点空间使用率更高,而又存有兄弟节点的指针,可以向兄弟节点转移关键字的特性使得B*树额分解次数变得更少

https://pic3.zhimg.com/80/v2-e8bf8ee3230f3d39d59ce5e76a2ee32e_720w.jpg

 

mysql为什么用b+树不用b树:

二叉树(可能出现全部在左边和右边的数据)——>AVL(平衡二叉树数据大量的时候平衡的时间太多,)——>B Tree(多路平衡查找树)(数据表中的数据都是存储在页中的,所以一个页中能存储多少行数据呢指针少的情况下要保存大量数据,只能增加树的高度,导致IO操作变多,查询性能变低)——>B+ Tree的一个演变的过程来进行分析,为什么使用B+ Tree的?B+ Tree,都放在了叶子节点上。提高了检索的效率。预读原理,因为B+ Tree无 data 域,其实就是因为没有date域了,但是每次IO的页的大小是固定的,每次IO读取若干个块块中包含的Key域的值肯定更多啊,B+树单次磁盘IO的信息量大于B树,从这点来看B+树相对B树磁盘 IO 次数少。利用了磁盘预读原理,将一个节点的大小设为等于一个页,这样每个节点只需要一次I/O就可以完全载入。1、B+Tree中因为数据都在叶子节点,所以每次查询的时间复杂度是固定的,因为稳定性保证了2、而且叶子节点之间都是链表的结构,所以B+Tree也是可以支持范围查询的,而B树每个节点 key 和 data 在一起,则无法区间查找。

数据库中的回表的原理以及回表的导致索引失效。

mysql的innodb引擎查询记录时在无法使用索引覆盖的场景下,需要做回表操作获取记录的所需字段。mysql执行sql前会执行sql优化、索引选择等操作,mysql会预估各个索引所需要的查询代价以及不走索引所需要的查询代价,从中选择一个mysql认为代价最小的方式进行sql查询操作。而在回表数据量比较大时,经常会出现mysql对回表操作查询代价预估代价过大而导致索引使用错误的情况。通俗的讲:如果索引的列在select所需获得列中就不需要回表(因为在mysql中索引是根据索引列的值进行排序的,所以索引节点中存在该列中的部分值),如果select所需获得列中有大量的非索引列,索引就需要到表中找到相应的列的信息,这就叫回表。

即当一条sql查询超过表中超过大概17%的记录且不能使用覆盖索引时,会出现索引的回表代价太大而选择全表扫描的现象。且这个比例随着单行记录的字节大小的增加而略微增大。

数据库的索引和优化

数据库索引其实就是为了使查询数据效率快。索引有哪些优缺点?

索引的优点:1可以大大加快数据的检索速度,这也是创建索引的最主要的原因。2通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。

索引的缺点:1时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/改/删的执行效率;2空间方面:索引需要占物理空间。

索引使用场景

索引覆盖:如果要查询的字段都建立过索引,那么引擎会直接在索引表中查询而不会访问原始数据(否则只要有一个字段没有建立索引就会做全表扫描),这叫索引覆盖。因此我们需要尽可能的在select后只写必要的查询字段,以增加索引覆盖的几率。这里值得注意的是不要想着为每个字段建立索引,因为优先使用索引的优势就在于其体积小。

索引的类型

主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。

唯一索引: 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。

可以通过 ALTER TABLE table_name ADD UNIQUE (column); 创建唯一索引

可以通过 ALTER TABLE table_name ADD UNIQUE (column1,column2); 创建唯一组合索引

普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值。

可以通过ALTER TABLE table_name ADD INDEX index_name (column);创建普通索引

可以通过ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);创建组合索引

全文索引:是目前搜索引擎使用的一种关键技术。可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引。

索引的数据结构

索引的数据结构和具体存储引擎的实现有关,在MySQL中使用较多的索引有Hash索引,B+树索引等,而我们经常使用的InnoDB存储引擎的默认索引实现为:B+树索引。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。

索引的原则:

选择唯一性索引唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。索引虽好,但也不是无限制的使用,最好符合一下几个原则:

1) 最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

2)较频繁作为查询条件的字段才去创建索引

3)更新频繁字段不适合创建索引

4)若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)

5)尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。

6)定义有外键的数据列一定要建立索引。

7)对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。

8)对于定义为text、image和bit的数据类型的列不要建立索引

索引算法

索引算法有 BTree算法和Hash算法

BTree算法

BTree是最常用的mysql数据库索引算法,也是mysql默认的算法。因为它不仅可以被用在=,>,>=,<,<=和between这些比较操作符上,而且还可以用于like操作符,只要它的查询条件是一个不以通配符开头的常量, 例如:-- 只要它的查询条件是一个不以通配符开头的常量

select * from user where name like 'jack%';

-- 如果一通配符开头,或者没有使用常量,则不会使用索引,例如:select * from user where name like '%jack';

Hash算法

Hash Hash索引只能用于对等比较,例如=,<=>(相当于=)操作符。由于是一次定位数据,不像BTree索引需要从根节点到枝节点,最后才能访问到页节点这样多次IO访问,所以检索效率远高于BTree索引。

创建索引的原则和使用原则

索引虽好,但也不是无限制的使用,最好符合一下几个原则

1)最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

2)较频繁作为查询条件的字段才去创建索引

3)更新频繁字段不适合创建索引

4)若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)

5)尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。

6)定义有外键的数据列一定要建立索引。

7)对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。

8)对于定义为text、image和bit的数据类型的列不要建立索引。

创建索引时需要注意什么?

非空字段:应该指定列为NOT NULL,除非你想存储NULL。在mysql中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值;

取值离散大的字段:(变量各个取值之间的差异程度)的列放到联合索引的前面,可以通过count()函数查看字段的差异值,返回值越大说明字段的唯一值越多字段的离散程度高;

索引字段越小越好:数据库的数据存储以页为单位一页存储的数据越多一次IO操作获取的数据越大效率越高

使用索引查询一定能提高查询的性能吗?

通常,通过索引查询数据比全表扫描要快。但是我们也必须注意到它的代价。

索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时,索引本身也会被修改。 这意味着每条记录的INSERT,DELETE,UPDATE将为此多付出4,5 次的磁盘I/O。 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。使用索引查询不一定能提高查询性能,索引范围查询(INDEX RANGE SCAN)适用于两种情况:

    基于一个范围的检索,一般查询返回结果集小于表中记录数的30%

    基于非唯一性索引的检索

百万级别或以上的数据如何删除

关于索引:由于索引需要额外的维护成本,因为索引文件是单独存在的文件,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。所以,在我们删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的。

所以我们想要删除百万数据的时候可以先删除索引(此时大概耗时三分多钟)

然后删除其中无用数据(此过程需要不到两分钟)

删除完成后重新创建索引(此时数据较少了)创建索引也非常快,约十分钟左右。

与之前的直接删除绝对是要快速很多,更别说万一删除中断,一切删除会回滚。那更是坑了。

前缀索引 什么是最左前缀原则?

顾名思义,就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。

最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。

=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式

聚簇索引与非聚簇索引

聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据

非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因

B+树在满足聚簇索引和覆盖索引的时候不需要回表查询数据,

在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引。 在InnoDB中,只有主键索引是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引。如果没有唯一键,隐式的生成一个键来建立聚簇索引。

当查询使用聚簇索引时,在对应的叶子节点,可以获取到整行数据,因不用再次进行回表查询。

非聚簇索引一定会回表查询吗?

不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再进行回表查询。

举个简单的例子,假设我们在员工表的年龄上建立了索引,那么当进行select age from employee where age < 20的查询时,在索引的叶子节点上,已经包含了age信息,不会再次进行回表查询。

联合索引是什么?为什么需要注意联合索引中的顺序?

MySQL可以使用多个字段同时建立一个索引,叫做联合索引。在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否则无法命中索引。这个遵循最左前缀原则。

SQL什么情况下不会使用索引(不包含,不等于,函数)

  1. 查询谓词没有使用索引的主要边界,换句话说就是select *,可能会导致不走索引。
  2. 单键值的b树索引列上存在null值,导致COUNT(*)不能走索引。索引列上有函数运算,导致不走索引
  3. 隐式转换导致不走索引。
  4. 表的数据库小或者需要选择大部分数据,不走索引
  5. cbo优化器下统计信息不准确,导致不走索引
  6. !=或者<>(不等于),可能导致不走索引,也可能走 INDEX FAST FULL SCAN
  7. 表字段的属性导致不走索引,字符型的索引列会导致优化器认为需要扫描索引大部分数据且聚簇因子很大,最终导致弃用索引扫描而改用全表扫描方式。
  8. 建立组合索引,但查询谓词并未使用组合索引的第一列,此处有一个INDEX SKIP SCAN概念,
  9. like '%liu' 百分号在前
  10. not in ,not exist

数据库的锁

3.1共享锁:

    叫做S锁 也是读锁。读锁就是其他人不能来修改这个锁。但是其他的人是可以进行读取的锁的。这是一种共享的锁的机制。

3.2排它锁:

    叫X锁 也就是写锁。其他人即不能读取这个数据也不能写这个数据。是一种的排他的锁的机制。也是不允许其他的线程读和修改的操作。

3.3意向锁

    意向共享锁:在拿到共享锁之前,需要时拿到这个意向共享锁。

    意向排他锁:在拿到排它锁之前,是需要拿到这个意向排它锁。

    为了在获得锁的时候能搞锁的获得的性能的一锁。

3.4自增锁

    就是在ID 在插入的数据的时候在删除了,再接着重新插入后ID不是连续的,而是在保持在删除后的操作的ID。

3.5临建锁

临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间。对在查询的时候大于小于查询的时候,采用了临建锁的话,那么就会在查询的主键的扩大到上下主键都会进行加锁的操作。这样导致锁的的范围扩大到原理的上下的主键的范围了。

3.6间隙锁

锁定的是一个范围,而且是不包括基本的本身。GAP的锁的目的就是为了防止同样的一个事务的出现重复度和幻读的情况。当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁。因为Query执行过程中通过范围查找的话,他会锁定整个范围内所有的索引键值,即使这个键值并不存在。间隙锁有一个比较致命的弱点,就是当锁定一个范围键值之后,即使某些不存在的键值也会被无辜的锁定,而造成在锁定的时候无法插入锁定值范围内的任何数据,在某些场景下这可能会针对性造成很大的危害。

3.7记录锁

记录锁,它封锁索引记录,例如:select * from t where id=1 for update;它会在id=1的索引记录上加锁,以阻止其他事务插入,更新,删除id=1的这一行。

3.8表锁

对一张表进行了表锁,这个其他的线程就不允许来修改这个表中的数据,但是可以做查询操作。

3.9行锁

在每一个单个的记录上的一个锁,就是不允许修改改行的数据的一种锁。锁定这一行记录,不允许其他人进行修改。

3.10读写锁

读锁就是其他人不能来修改这个锁。但是其他的人是可以进行读取的锁的。这是一种共享的锁的机制。

写锁:其他人即不能读取这个数据也不能写这个数据。是一种的排他的锁的机制。

3.11MVCC:

多版本并发控制,提供并发访问数据库时,对事务内读取的到的内存做处理,用来避免写操作堵塞读操作的并发问题。

MVCC有两种实现方式,第一种实现方式是将数据记录的多个版本保存在数据库中,当这些不同版本数据不再需要时,垃圾收集器回收这些记录。这个方式被PostgreSQL和Firebird/Interbase采用。

第二种实现方式只在数据库保存最新版本的数据,但是会在使用undo时动态重构旧版本数据,这种方式被Oracle和MySQL/InnoDB使用。

MVCC的实现大都都实现了非阻塞的读操作,写操作也只锁定必要的行。InnoDB的MVCC实现,是通过保存数据在某个时间点的快照来实现的。一个事务,不管其执行多长时间,其内部看到的数据是一致的。也就是事务在执行的过程中不会相互影响。下面我们简述一下MVCC在InnoDB中的实现。

InnoDB的MVCC,通过在每行记录后面保存两个隐藏的列来实现:一个保存了行的创建时间,一个保存行的过期时间(删除时间),当然,这里的时间并不是时间戳,而是系统版本号,每开始一个新的事务,系统版本号就会递增。

select操作。

InnoDB只查找版本早于(包含等于)当前事务版本的数据行。可以确保事务读取的行,要么是事务开始前就已存在,或者事务自身插入或修改的记录。行的删除版本要么未定义,要么大于当前事务版本号。可以确保事务读取的行,在事务开始之前未删除。

    insert操作。将新插入的行保存当前版本号为行版本号。

    delete操作。将删除的行保存当前版本号为删除标识。

    update操作。变为insert和delete操作的组合,insert的行保存当前版本号为行版本号,delete则保存当前版本号到原来的行作为删除标识。

由于旧数据并不真正的删除,所以必须对这些数据进行清理,innodb会开启一个后台线程执行清理工作,具体的规则是将删除版本号小于当前系统版本的行删除,这个过程叫做purge。

乐观锁和悲观锁区别

悲观锁:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制

乐观锁:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:乐一般会使用版本号机制或CAS算法实现。

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

数据库的事务

6.1数据库的ACID是什么

一个事务本质上有四个特点ACID:原子性、一致性、隔离性、持久性

原子性(Atomic):

事务中各项操作,要么全做要么全不做,任何一项操作的失败都会导致整个事务的失败,就像原子一样不可分割;

一致性(Consistent):

事务结束后系统状态是一致的;一个事务可以封装状态改变(除非它是一个只读的)。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。

举个例子:如果事务是并发多个,系统也必须如同串行事务一样操作。其主要特征是保护性和不变性(Preserving an Invariant),以转账案例为例,假设有五个账户,每个账户余额是100元,那么五个账户总额是500元,如果在这个5个账户之间同时发生多个转账,无论并发多少个,比如在A与B账户之间转账5元,在C与D账户之间转账10元,在B与E之间转账15元,五个账户总额也应该还是500元,这就是保护性和不变性。

隔离性(Isolated):

并发执行的事务彼此无法看到对方的中间状态;隔离状态执行事务,使它们好像是系统在给定时间内执行的唯一操作。如果有两个事务,运行在相同的时间内,执行相同的功能,事务的隔离性将确保每一事务在系统中认为只有该事务在使用系统。这种属性有时称为串行化,为了防止事务操作间的混淆,必须串行化或序列化请求,使得在同一时间仅有一个请求用于同一数据。

持久性(Durable):

事务完成后所做的改动都会被持久化即使发生灾难性的失败。通过日志和同步备份可以在故障发生后重建数据。在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库之中,并不会被回滚。由于一项操作通常会包含许多子操作,而这些子操作可能会因为硬件的损坏或其他因素产生问题,要正确实现ACID并不容易。ACID建议数据库将所有需要更新以及修改的资料一次操作完毕,但实际上并不可行。目前主要有两种方式实现ACID:第一种是Write ahead logging,也就是日志式的方式(现代数据库均基于这种方式)。第二种是Shadow paging。

相对于WAL(write ahead logging)技术,shadow paging技术实现起来比较简单,消除了写日志记录的开销恢复的速度也快(不需要redo和undo)。shadow paging的缺点就是事务提交时要输出多个块,这使得提交的开销很大,而且以块为单位,很难应用到允许多个事务并发执行的情况——这是它致命的缺点。WAL 的中心思想是对数据文件 的修改(它们是表和索引的载体)必须是只能发生在这些修改已经 记录了日志之后 -- 也就是说,在日志记录冲刷到永久存储器之后.如果我们遵循这个过程,那么我们就不需要在每次事务提交的时候 都把数据页冲刷到磁盘,因为我们知道在出现崩溃的情况下, 我们可以用日志来恢复数据库:任何尚未附加到数据页的记录 都将先从日志记录中重做(这叫向前滚动恢复,也叫做 REDO) 然后那些未提交的事务做的修改将被从数据页中删除 (这叫向后滚动恢复 - UNDO)。

6.2数据库隔离级别

一般的数据库,都包括以下四种隔离级别:

读未提交(Read Uncommitted)

读未提交,顾名思义,就是可以读到未提交的内容。因此,在这种隔离级别下,查询是不会加锁的,也由于查询的不加锁,所以这种隔离级别的一致性是最差的,可能会产生“脏读”、“不可重复读”、“幻读”。如无特殊情况,基本是不会使用这种隔离级别的。

读提交(Read Committed)

这是各种系统中最常用的一种隔离级别,也是SQL Server和Oracle的默认隔离级别。这种隔离级别能够有效的避免脏读,但除非在查询中显示的加锁,如:

select * from T where ID=2 lock in share mode;

select * from T where ID=2 for update;

不然,普通的查询是不会加锁的。

那为什么“读提交”同“读未提交”一样,都没有查询加锁,但是却能够避免脏读呢?这就要说道另一个机制“快照(snapshot)”,而这种既能保证一致性又不加锁的读也被称为“快照读(Snapshot Read)

可重复读(Repeated Read)

就是专门针对“不可重复读”这种情况而制定的隔离级别,自然,它就可以有效的避免“不可重复读”。而它也是MySql的默认隔离级别。在这个级别下,普通的查询同样是使用的“快照读”,但是,和“读提交”不同的是,当事务启动时,就不允许进行“修改操作(Update)”了,而“不可重复读”恰恰是因为两次读取之间进行了数据的修改,因此,“可重复读”能够有效的避免“不可重复读”,但却避免不了“幻读”,因为幻读是由于“插入或者删除操作(Insert or Delete)”而产生的。

串行化(Serializable)

这种级别下,“脏读”、“不可重复读”、“幻读”都可以被避免,但是执行效率奇差,性能开销也最大,所以基本没人会用。

5.3事务出现问题以及解决方法

脏读是指当一个事务正在访问数据,并且对数据进行了修改。而这种修改还没有提交到数据库中,这时,另外一个事务也访问了这个数据,然后使用了这个数据。

例子:

1.财务将董震的工资从1000修改成了8000(但未提交事务)

2.此时应董震读取自己的工资发现自己的工资变成了8000,高兴的上蹦下跳

3.接着财务发现操作有误,回滚了事务,此时董震的工资又变成了1000,此时董震记取的工资8000是一个 脏数据

不可重复度

事务A访问了数据库,他要查看ID是1的牛人的名字,于是执行了

select Name from T where ID = 1;

这时,事务B来了,因为ID是1的牛人改名字了,所以要更新一下,然后提交了事务。

update T set Name = '不牛' where ID = 1;

接着,事务A还想再看看ID是1的牛人的名字,于是又执行了

select Name from T where ID = 1;

结果,两次读出来的ID是1的牛人名字竟然不相同,这就是不可重复读(unrepeatable read)

幻读是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到了表中的全部数据行。同时,第二个事务也修改了这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好像发生了幻觉一样。

例子:目前公司员工工资为1000的有10人

1.事务1读取所有的员工工资为1000的员工。

2.这时事务2向employee表插入了一条员工纪录,工资也为1000

3.事务1再次读取所有工资为1000的员工,共读取了11条记录。

解决方法:如果在操作事务完成数据处理之前,任何其它事务都不可以添加新数据。

数据库的复制备份方案

主从复制:将主数据库中的DDL和DML操作通过二进制日志(BINLOG)传输到从数据库上,然后将这些日志重新执行(重做);从而使得从数据库的数据与主数据库保持一致。

主从复制的作用:

    主数据库出现问题,可以切换到从数据库。

    可以进行数据库层面的读写分离。

    可以在从数据库上进行日常备份。

MySQL主从复制解决的问题

    数据分布:随意开始或停止复制,并在不同地理位置分布数据备份

    负载均衡:降低单个服务器的压力

    高可用和故障切换:帮助应用程序避免单点失败

 升级测试:可以用更高版本的MySQL作为从库

MySQL主从复制工作原理

    在主库上把数据更高记录到二进制日志

    从库将主库的日志复制到自己的中继日志

从库读取中继日志的事件,将其重放到从库数据中

https://img-blog.csdn.net/20180313230215773

1MYSQL的一主一从备份

主从复制:将主数据库中的DDL和DML操作通过二进制日志(BINLOG)传输到从数据库上,然后将这些日志重新执行(重做);从而使得从数据库的数据与主数据库保持一致。

主从复制的作用:1主数据库出现问题,可以切换到从数据库。2可以进行数据库层面的读写分离。3可以在从数据库上进行日常备份。

MySQL主从复制解决的问题:数据分布:随意开始或停止复制,并在不同地理位置分布数据备份2:负载均衡:降低单个服务器的压力3:高可用和故障切换:帮助应用程序避免单点失败4升级测试:可以用更高版本的MySQL作为从库。

MySQL主从复制工作原理:1在主库上把数据更高记录到二进制日志2从库将主库的日志复制到自己的中继日志。3从库读取中继日志的事件,将其重放到从库数据中。基本原理流程,3个线程以及之间的关联

主:binlog线程——记录下所有改变了数据库数据的语句,放进master上的binlog中;

从:io线程——在使用start slave 之后,负责从master上拉取 binlog 内容,放进自己的relay log中;从:sql执行线程——执行relay log中的语句;

Binary log:主数据库的二进制日志

Relay log:从服务器的中继日志

第一步:master在每个事务更新数据完成之前,将该操作记录串行地写入到binlog文件中。

第二步:salve开启一个I/O Thread,该线程在master打开一个普通连接,主要工作是binlog dump process。如果读取的进度已经跟上了master,就进入睡眠状态并等待master产生新的事件。I/O线程最终的目的是将这些事件写入到中继日志中。

第三步:SQL Thread会读取中继日志,并顺序执行该日志中的SQL事件,从而与主数据库中的数据保持一致。

读写分离有哪些解决方案?

读写分离是依赖于主从复制,而主从复制又是为读写分离服务的。因为主从复制要求slave不能写只能读(如果对slave执行写操作,那么show slave status将会呈现Slave_SQL_Running=NO,此时你需要按照前面提到的手动同步一下slave)。

方案一:使用mysql-proxy代理

优点:直接实现读写分离和负载均衡,不用修改代码,master和slave用一样的帐号,mysql官方不建议实际生产中使用

缺点:降低性能, 不支持事务

方案二:使用AbstractRoutingDataSource+aop+annotation在dao层决定数据源。

如果采用了mybatis, 可以将读写分离放在ORM层,比如mybatis可以通过mybatis plugin拦截sql语句,所有的insert/update/delete都访问master库,所有的select 都访问salve库,这样对于dao层都是透明。 plugin实现时可以通过注解或者分析语句是读写方法来选定主从库。不过这样依然有一个问题, 也就是不支持事务, 所以我们还需要重写一下DataSourceTransactionManager,将read-only的事务扔进读库,其余的有读有写的扔进写库。

方案三:使用AbstractRoutingDataSource+aop+annotation在service层决定数据源,可以支持事务.

缺点:类内部方法通过this.xx()方式相互调用时,aop不会进行拦截,需进行特殊处理。

备份计划,mysqldump以及xtranbackup的实现原理

(1)备份计划

视库的大小来定,一般来说 100G 内的库,可以考虑使用 mysqldump 来做,因为 mysqldump更加轻巧灵活,备份时间选在业务低峰期,可以每天进行都进行全量备份(mysqldump 备份出来的文件比较小,压缩之后更小)。

100G 以上的库,可以考虑用 xtranbackup 来做,备份速度明显要比 mysqldump 要快。一般是选择一周一个全备,其余每天进行增量备份,备份时间为业务低峰期。

(2)备份恢复时间

物理备份恢复快,逻辑备份恢复慢

这里跟机器,尤其是硬盘的速率有关系,以下列举几个仅供参考

20G的2分钟(mysqldump)/80G的30分钟(mysqldump)/111G的30分钟(mysqldump)/288G的3小时(xtra)/3T的4小时(xtra)逻辑导入时间一般是备份时间的5倍以上

(3)备份恢复失败如何处理

首先在恢复之前就应该做足准备工作,避免恢复的时候出错。比如说备份之后的有效性检查、权限检查、空间检查等。如果万一报错,再根据报错的提示来进行相应的调整。

(4)mysqldump和xtrabackup实现原理

mysqldump

mysqldump 属于逻辑备份。加入–single-transaction 选项可以进行一致性备份。后台进程会先设置 session 的事务隔离级别为 RR(SET SESSION TRANSACTION ISOLATION LEVELREPEATABLE READ),之后显式开启一个事务(START TRANSACTION /*!40100 WITH CONSISTENTSNAPSHOT */),这样就保证了该事务里读到的数据都是事务事务时候的快照。之后再把表的数据读取出来。如果加上–master-data=1 的话,在刚开始的时候还会加一个数据库的读锁(FLUSH TABLES WITH READ LOCK),等开启事务后,再记录下数据库此时 binlog 的位置(showmaster status),马上解锁,再读取表的数据。等所有的数据都已经导完,就可以结束事务

Xtrabackup:xtrabackup 属于物理备份,直接拷贝表空间文件,同时不断扫描产生的 redo 日志并保存下来。最后完成 innodb 的备份后,会做一个 flush engine logs 的操作(老版本在有 bug,在5.6 上不做此操作会丢数据),确保所有的 redo log 都已经落盘(涉及到事务的两阶段提交

概念,因为 xtrabackup 并不拷贝 binlog,所以必须保证所有的 redo log 都落盘,否则可能会丢最后一组提交事务的数据)。这个时间点就是 innodb 完成备份的时间点,数据文件虽然不是一致性的,但是有这段时间的 redo 就可以让数据文件达到一致性(恢复的时候做的事

情)。然后还需要 flush tables with read lock,把 myisam 等其他引擎的表给备份出来,备份完后解锁。这样就做到了完美的热备。

 MYSQL的双主双从备份

 MYSQL的半同步复制与并行复制

MYSQL的主从延迟怎么解决。

实际上主从同步延迟根本没有什么一招制敌的办法,因为所有的SQL必须都要在从服务器里面执行一遍,但是主服务器如果不断的有更新操作源源不断的写入, 那么一旦有延迟产生,那么延迟加重的可能性就会原来越大。 当然我们可以做一些缓解的措施

最简单的减少slave同步延时的方案就是在架构上做优化,尽量让主库的DDL快速执行还有就是主库是写,对数据安全性较高,比如sync_binlog=1innodb_flush_log_at_trx_commit = 1 之类的设置,而slave则不需要这么高的数据安全,完全可以讲sync_binlog设置为0或者关闭binlog,innodb_flushlog也 可以设置为0来提高sql的执行效率。另外就是使用比主库更好的硬件设备作为slave。把一台从服务器当作备份使用,而不提供查询,这样他的负载就下来了, 执行relay log 里面的SQL效率自然就高了。增加从服务器,这个目的还是分散读的压力, 从而降低服务器负载

在高并发场景下,从库的数据一定会比主库慢一些,是有延时的。所以经常出现,刚写入主库的数据可能是读不到的,要过几十毫秒,甚至几百毫秒才能读取到。这里还有另外一个问题,就是如果主库突然宕机,然后恰好数据还没同步到从库,那么有些数据可能在从库上是没有的,有些数据可能就丢失了。所以 MySQL 实际上在这一块有两个机制,一个是半同步复制,用来解决主库数据丢失问题;一个是并行复制,用来解决主从同步延时问题。

半同步复制:指的就是主库写入 binlog 日志之后,就会将强制此时立即将数据同步到从库,从库将日志写入自己本地的 relay log 之后,接着会返回一个 ack 给主库,主库接收到至少一个从库的 ack 之后才会认为写操作完成了。

并行复制:指的是从库开启多个线程,并行读取relay log中不同库的日志,然后并行重放不同库的日志,这是库级别的并行。

MySql数据库从库同步其他问题及解决方案

1)、mysql主从复制存在的问题:  主库宕机后,数据可能丢失 从库只有一个sql Thread,主库写压力大,复制很可能延时

2)、解决方法: 半同步复制---解决数据丢失的问题 并行复制----解决从库复制延迟的问题

https://img-blog.csdn.net/20180313233521954

3)、半同步复制mysql semi-sync(半同步复制)半同步复制:  5.5集成到mysql,以插件的形式存在,需要单独安装 确保事务提交后binlog至少传输到一个从库 不保证从库应用完这个事务的binlog 性能有一定的降低,响应时间会更长 网络异常或从库宕机,卡主主库,直到超时或从库恢复4)、主从复制--异步复制原理、半同步复制和并行复制原理比较

https://img-blog.csdn.net/2018031323380334

MySQL的binlog有有几种录入格式?分别有什么区别?

有三种格式,statement,row和mixed。

    statement模式下,每一条会修改数据的sql都会记录在binlog中。不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。由于sql的执行是有上下文的,因此在保存的时候需要保存相关的信息,同时还有一些使用了函数之类的语句无法被记录复制。

    row级别下,不记录sql语句上下文相关信息,仅保存哪条记录被修改。记录单元为每一行的改动,基本是可以全部记下来但是由于很多操作,会导致大量行的改动(比如alter table),因此这种模式的文件保存的信息太多,日志量太大。

    mixed,一种折中的方案,普通操作使用statement记录,当无法使用statement的时候使用row。

此外,新版的MySQL中对row级别也做了一些优化,当表结构发生变化的时候,会记录语句而不是逐行记录。

数据库的集群

分布式数据库MongoDB

MongoDB 是由 C++语言编写的,是一个基于分布式文件存储的开源数据库系统。 在高负载的情况下,添加更多的节点,可以保证服务器性能。 MongoDB 旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。MongoDB 将数据存储为一个文档,数据结构由键值(key=>value)对组成。 MongoDB 文档类似于 JSON 对象。字段值可以包含其他文档,数组及文档数组。

特点

MongoDB 是一个面向文档存储的数据库,操作起来比较简单和容易。

你可以在 MongoDB 记录中设置任何属性的索引 (如: FirstName="Sameer",Address="8 Ga

ndhi Road")来实现更快的排序。

你可以通过本地或者网络创建数据镜像,这使得 MongoDB 有更强的扩展性。

如果负载的增加(需要更多的存储空间和更强的处理能力) ,它可以分布在计算机网络中的其他节点上这就是所谓的分片。

Mongo 支持丰富的查询表达式。查询指令使用 JSON 形式的标记,可轻易查询文档中内嵌的

对象及数组。

MongoDb 使用 update()命令可以实现替换完成的文档(数据)或者一些指定的数据字段 。

Mongodb 中的 Map/reduce 主要是用来对数据进行批量处理和聚合操作。

Map 和 Reduce。 Map 函数调用 emit(key,value)遍历集合中所有的记录,将 key 与 value 传给 Reduce 函数进行处理。

Map 函数和 Reduce 函数是使用 Javascript 编写的,并可以通过 db.runCommand 或 mapre

duce 命令来执行 MapReduce 操作。13/04/2018 Page 191 of 283

GridFS 是 MongoDB 中的一个内置功能,可以用于存放大量小文件。

MongoDB 允许在服务端执行脚本, 可以用 Javascript 编写某个函数,直接在服务端执行,也可以把函数的定义存储在服务端,下次直接调用即可。

show dbs: 现实数据库列表

show collections: 显示当前数据库中的集合(类似关系数据库中的表)

show users: 显示用户

use <db name>: 切换当前数据库

db.help(): 显示数据库操作命令,里面有很多的命令

db.foo.help():显示集合操作命令,同样有很多的命令,foo指的是当前数据库下,一个叫foo的集合,并非真正意义上的命令

db.foo.find(): 对当前数据库中的foo集合进行数据查找

db.foo.find({a:1})

1, Help查看命令提示

help

db.help()

db.yourColl.help()

db.yourColl.find()

rs.help()

2, 切换/创建数据库

use yourDB  当创建一个集合(table)的时候会自动创建当前数据库

3,查询所有数据库

show dbs;

4,删除当前使用的数据库

db.dropDatabase()

5,从指定主机上克隆数据库

db.cloneDatabase("127.0.0.1")

6,从指定的机器上复制指定数据库数据到某个数据库

db.cloneDatabase("mydb", "temp", "127.0.0.1");将本机的mydb的数据复制到temp数据库中

7, 修复当前数据库

db.repairDatabase()

8, 查看当前使用的数据库

db.getName()

9,显示当前db状态

db.stats()

10, 当前db

db.version()

11, 查看当前db的连接机器地址

db.getMongo();

Collection聚集集合

1, 创建一个聚集集合(table)

db.createCollection("collName", {size:20, capped:5,max:100})

2, 得到指定名称的聚集集合(table)

get.getCollection("account");

3, 得到当前db的所有聚集集合

db.getCollectionNames();

4, 显示当前db所有聚焦索引的状态

db.printCollectionStats();

用户相关

1,添加一个用户

db.addUser("name")

db.addUser("userName". "password", true); 添加用户,设置密码,是否只读

2,数据库认证,安全模式

db.auth("useName", "123123")

3, 显示当前所有用户

show users;

4, 删除用户

db.removeUser("userName")

其他

1, 查询之前的错误信息

db.getPrevError()

2, 清楚错误记录

db.resetError()

1、查询所有记录

db.userInfo.find();

相当于:select* from userInfo;

默认每页显示20条记录,当显示不下的情况下,可以用it迭代命令查询下一页数据。注意:键入it命令不能带“;”

但是你可以设置每页显示数据的大小,用DBQuery.shellBatchSize= 50;这样每页就显示50条记录了。

2、查询去掉后的当前聚集集合中的某列的重复数据

db.userInfo.distinct("name");

会过滤掉name中的相同数据

相当于:select distict name from userInfo;

3、查询age = 22的记录

db.userInfo.find({"age": 22});

相当于: select * from userInfo where age = 22;

4、查询age > 22的记录

db.userInfo.find({age: {$gt: 22}});

相当于:select * from userInfo where age >22;

5、查询age < 22的记录

db.userInfo.find({age: {$lt: 22}});

相当于:select * from userInfo where age <22;

6、查询age >= 25的记录

db.userInfo.find({age: {$gte: 25}});

相当于:select * from userInfo where age >= 25;

7、查询age <= 25的记录

db.userInfo.find({age: {$lte: 25}});

8、查询age >= 23 并且 age <= 26

db.userInfo.find({age: {$gte: 23, $lte: 26}});

9、查询name中包含 mongo的数据

db.userInfo.find({name: /mongo/});

//相当于%%

select * from userInfo where name like ‘%mongo%’;

10、查询name中以mongo开头的

db.userInfo.find({name: /^mongo/});

select * from userInfo where name like ‘mongo%’;

11、查询指定列name、age数据

db.userInfo.find({}, {name: 1, age: 1});

相当于:select name, age from userInfo;

当然name也可以用true或false,当用ture的情况下河name:1效果一样,如果用false就是排除name,显示name以外的列信息。

12、查询指定列name、age数据, age > 25

db.userInfo.find({age: {$gt: 25}}, {name: 1, age: 1});

相当于:select name, age from userInfo where age >25;

13、按照年龄排序

升序:db.userInfo.find().sort({age: 1});

降序:db.userInfo.find().sort({age: -1});

14、查询name = zhangsan, age = 22的数据

db.userInfo.find({name: 'zhangsan', age: 22});

相当于:select * from userInfo where name = ‘zhangsan’ and age = ‘22’;

15、查询前5条数据

db.userInfo.find().limit(5);

相当于:selecttop 5 * from userInfo;

16、查询10条以后的数据

db.userInfo.find().skip(10);

相当于:select * from userInfo where id not in (selecttop 10 * from userInfo);

17、查询在5-10之间的数据

db.userInfo.find().limit(10).skip(5);

可用于分页,limit是pageSize,skip是第几页*pageSize

18、or与 查询

db.userInfo.find({$or: [{age: 22}, {age: 25}]});

相当于:select * from userInfo where age = 22 or age = 25;

19、查询第一条数据

db.userInfo.findOne();

相当于:selecttop 1 * from userInfo;

db.userInfo.find().limit(1);

20、查询某个结果集的记录条数

db.userInfo.find({age: {$gte: 25}}).count();

相当于:select count(*) from userInfo where age >= 20;

21、按照某列进行排序

db.userInfo.find({sex: {$exists: true}}).count();

相当于:select count(sex) from userInfo;

1、创建索引

db.userInfo.ensureIndex({name: 1});

db.userInfo.ensureIndex({name: 1, ts: -1});

2、查询当前聚集集合所有索引

db.userInfo.getIndexes();

3、查看总索引记录大小

db.userInfo.totalIndexSize();

4、读取当前集合的所有index信息

db.users.reIndex();

5、删除指定索引

db.users.dropIndex("name_1");

6、删除所有索引索引

db.users.dropIndexes();

1、添加

db.users.save({name: ‘zhangsan’, age: 25, sex: true});

添加的数据的数据列,没有固定,根据添加的数据为准

2、修改

db.users.update({age: 25}, {$set: {name: 'changeName'}}, false, true);

相当于:update users set name = ‘changeName’ where age = 25;

db.users.update({name: 'Lisi'}, {$inc: {age: 50}}, false, true);

相当于:update users set age = age + 50 where name = ‘Lisi’;

db.users.update({name: 'Lisi'}, {$inc: {age: 50}, $set: {name: 'hoho'}}, false, true);

相当于:update users set age = age + 50, name = ‘hoho’ where name = ‘Lisi’;

3、删除

db.users.remove({age: 132});

4、查询修改删除

db.users.findAndModify({

    query: {age: {$gte: 25}},

    sort: {age: -1},

    update: {$set: {name: 'a2'}, $inc: {age: 2}},

    remove: true

});

db.runCommand({ findandmodify : "users",

    query: {age: {$gte: 25}},

    sort: {age: -1},

    update: {$set: {name: 'a2'}, $inc: {age: 2}},

    remove: true

});

update 或 remove 其中一个是必须的参数; 其他参数可选。

数据库高级中间件mycat

数据库解决方案

优化SQL语句定位

MySQL提供了explain命令来查看语句的执行计划。而执行计划,显示数据库引擎对于SQL语句的执行的详细情况,其中包含了是否使用索引,使用什么索引,使用的索引的相关信息等。

Type:(非常重要,可以看到有没有走索引)访问类型

    ALL 扫描全表数据

    index 遍历索引

    range 索引范围查找

    index_subquery 在子查询中使用 ref

    unique_subquery 在子查询中使用 eq_ref

    ref_or_null 对Null进行索引的优化的 ref

    fulltext 使用全文索引

    ref 使用非唯一索引查找数据

    eq_ref 在join查询中使用PRIMARY KEYorUNIQUE NOT NULL索引关联。

possible_keys:可能使用的索引,注意不一定会使用。查询涉及到的字段上若存在索引,则该索引将被列出来。当该列为 NULL时就要考虑当前的SQL是否需要优化了。

Key:显示MySQL在查询中实际使用的索引,若没有使用索引,显示为NULL。

TIPS:查询中若使用了覆盖索引(覆盖索引:索引的数据覆盖了需要查询的所有数据),则该索引仅出现在key列表中

key_length:索引长度

ref:表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值

rows:返回估算的结果集数目,并不是一个准确的值。

Extra:的信息非常丰富,常见的有:

    Using index 使用覆盖索引

    Using where 使用了用where子句来过滤结果集

    Using filesort 使用文件排序,使用非索引列进行时出现,非常消耗性能,尽量优化。

    Using temporary 使用了临时表 sql优化的目标可以参考阿里开发手册

大表数据查询优化

    优化shema、sql语句+索引;

    第二加缓存,memcached, redis;

    主从复制,读写分离;

    垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;

    水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key, 为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表;

开启慢日志查询

用于记录执行时间超过某个临界值的SQL日志,用于快速定位慢查询,为我们的优化做参考。

慢查询的优化首先要搞明白慢的原因是什么? 是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?

所以优化也是针对这三个方向来的,

    首先分析语句,看看是否load了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要的列,对语句进行分析以及重写。

    分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。

    如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。

字段优化查方法

1尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED

2VARCHAR的长度只分配真正需要的空间

3使用枚举或整数代替字符串类型

4尽量使用TIMESTAMP而非DATETIME,

5单表不要有太多字段,建议在20以内

6避免使用NULL字段,很难查询优化且占用额外索引空间

7用整型来存IP

8将大字段、访问频率低的字段拆分到单独的表中存储,分离冷热数据。

9禁止在数据库中存储明文密码。

10表必须有主键,推荐使用UNSIGNED自增列作为主键。

11禁止冗余索引。

12禁止重复索引

13不在低基数列上建立索引,例如“性别”。

14合理使用覆盖索引减少IO,避免排序。

15用IN代替OR。SQL语句中IN包含的值不应过多,应少于1000个。

16表字符集使用UTF8,必要时可申请使用UTF8MB4字符集。

17建议使用合理的分页方式以提高分页效率。

18 SELECT只获取必要的字段,禁⽌止使用SELECT *

19 采用合适的分库分表策略。例如千库十表、十库百表等。

20 减少与数据库交互次数,尽量采用批量SQL语句。

21 采用合适的分库分表策略。例如千库十表、十库百表等。

数据库索引优化

索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描。

应避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引进行全表扫描。

值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段

字符字段只建前缀索引

字符字段最好不要做主键

不用外键,由程序保证约束

尽量不用UNIQUE,由程序保证约束

使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引

引擎选择:

MyISAM:MyISAM引擎是MySQL 5.1及之前版本的默认引擎,它的特点是:

1不支持行锁,读取时对需要读到的所有表加锁,写入时则对表加排它锁

2不支持事务

3不支持外键

4不支持崩溃后的安全恢复

5在表有读取查询的同时,支持往表中插入新纪录

6支持BLOB和TEXT的前500个字符索引,支持全文索引

7支持延迟更新索引,极大提升写入性能

8对于不会进行修改的表,支持压缩表,极大减少磁盘空间占用

InnoDB:InnoDB在MySQL 5.5后成为默认索引,它的特点是:

1支持行锁,采用MVCC来支持高并发

2支持事务

3支持外键

4支持崩溃后的安全恢复

5不支持全文索引 总体来讲,MyISAM适合SELECT密集型的表,而InnoDB适合INSERT和UPDATE密集型的表

数据库读写分离

也是目前常用的优化,从库读主库写,一般不要采用双主或多主引入很多复杂性,尽量采用文中的其他方案来提高性能。同时目前很多拆分的解决方案同时也兼顾考虑了读写分离。

数据库分区分库分表

表分区:MySQL在5.1版引入的分区是一种简单的水平拆分,用户需要在建表的时候加上分区参数,对应用是透明的无需修改代码。

水平切用于分表:分库就是根据业务耦合性,将关联度低的不同表存储在不同的数据库,做法与大系统拆分为多个小系统类似,按业务分类进行独立划分。与“微服务治理”的做法相似,每个微服务使用单独的一个数据库。

垂直分表分库操作:是基于数据库中的列进行,某个表字段较多,可以新建一张扩展表,将不经常用或者字段长度较大的字段拆出到扩展表中。在字段很多的情况下,通过大表拆小表,更便于开发与维护,也能避免跨页问题。

分库分表的策略:

1最简单的都是可以通过取模的方式进行路由

2 Reange范围:按照时间id 年份的来划分

分片原则

能不分就不分,参考单表优化

分片数量尽量少,分片尽量均匀分布在多个数据结点上,因为一个查询SQL跨分片越多,则总体性能越差,虽然要好于所有数据在一个分片的结果,在必要的时候进行扩容,增加分片数量

分片规则需要慎重选择做好提前规划,分片规则的选择,需要考虑数据的增长模式,数据的访问模式,分片关联性问题,以及分片扩容问题,最近的分片策略为范围分片,枚举分片,一致性Hash分片,这几种分片都有利于扩容

尽量不要在一个事务中的SQL跨越多个分片,分布式事务一直是个不好处理的问题

查询条件尽量优化,尽量避免Select * 的方式,大量数据结果集下,会消耗大量带宽和CPU资源,查询尽量避免返回大量结果集,并且尽量为频繁使用的查询语句建立索引。

通过数据冗余和表分区赖降低跨库Join的可能。

这里特别强调一下分片规则的选择问题,如果某个表的数据有明显的时间特征,比如订单、交易记录等,则他们通常比较合适用时间范围分片,因为具有时效性的数据,我们往往关注其近期的数据,查询条件中往往带有时间字段进行过滤,比较好的方案是,当前活跃的数据,采用跨度比较短的时间段进行分片,而历史性的数据,则采用比较长的跨度存储。

总体上来说,分片的选择是取决于最频繁的查询SQL的条件,因为不带任何Where语句的查询SQL,会遍历所有的分片,性能相对最差,因此这种SQL越多,对系统的影响越大,所以我们要尽量避免这种SQL的产生。

分库分表线上部署

1停机迁移方案

2简单来说,就是在线上系统里面,之前所有写库的地方,增删改操作,都除了对老库增删改,都加上对新库的增删改,这就是所谓双写,同时写俩库,老库和新库。

分库分表问题解决方案

1跨库关联查询:

字段冗余:比如我们查询合同库的合同表的时候需要关联客户库的客户表,我们可以直接把一些经常关联查询的客户字段放到合同表,通过这种方式避免跨库关联查询的问题

数据同步:比如商户系统要查询产品系统的产品表,我们干脆在商户系统创建一张产品表,通过ETL 或者其他方式定时同步产品数据。

全局表(广播表):比如行名行号信息被很多业务系统用到,如果我们放在核心系统,每个系统都要去关联查询,这个时候我们可以在所有的数据库都存储相同的基础数据。

ER 表(绑定表):将一下存在逻辑外键的一下表格放置在同一个数据库中。

2分布式事务

全局事务(比如XA 两阶段提交;应用、事务管理器(TM)、资源管理器(DB))

基于可靠消息服务的分布式事务

柔性事务TCC

3排序、翻页、函数计算问题

跨节点多库进行查询时,会出现limit 分页,order by 排序的问题。max、min、sum、count 之类的函数在进行计算的时候,也需要先在每个分片上执行相应的函数,然后将各个分片的结果集进行汇总和再次计算,最终将结果返回

4全局主键避重问题:(MySQL 的数据库里面字段有一个自增的属性,Oracle 也有Sequence 序列。如果是一个数据库,那么可以保证ID 是不重复的,但是水平分表以后,每个表都按照自己的规律自增,肯定会出现ID 重复的问题,这个时候我们就不能用本地自增的方式了):

利用UUID(Universally Unique Identifier 通用唯一识别码)

把序号维护在数据库的一张表中。这张表记录了全局主键的类型、位数、起始值。

雪花算法Snowflake(64bit)

数据的缓存实现

缓存可以发生在这些层次:MySQL内部:在系统调优参数介绍了相关设置

数据访问层:比如MyBatis针对SQL语句做缓存,而Hibernate可以精确到单个记录,这里缓存的对象主要是持久化对象Persistence Object

应用服务层:这里可以通过编程手段对缓存做到更精准的控制和更多的实现策略,这里缓存的对象是数据传输对象Data Transfer Object

Web层:针对web页面做缓存

浏览器客户端:用户端的缓存

直写式(Write Through):在数据写入数据库后,同时更新缓存,维持数据库与缓存的一致性。这也是当前大多数应用缓存框架如Spring Cache的工作方式。这种实现非常简单,同步好,但效率一般。

回写式(Write Back):当有数据要写入数据库时,只会更新缓存,然后异步批量的将缓存数据同步到数据库上。这种实现比较复杂,需要较多的应用逻辑,同时可能会产生数据库与缓存的不同步,但效率非常高。

数据库集群方案

使用的数据库集群的方案来提高和优化数据库。基于HA的机制的mycat的高可用

升级硬件

Scale up,这个不多说了,根据MySQL是CPU密集型还是I/O密集型,通过提升CPU和内存、使用SSD,都能显著提升MySQL性能

其他知识

MyISAM索引与InnoDB索引的区别?

InnoDB索引是聚簇索引,MyISAM索引是非聚簇索引。

InnoDB的主键索引的叶子节点存储着行数据,因此主键索引非常高效。

MyISAM索引的叶子节点存储的是行数据地址,需要再寻址一次才能得到数据。

InnoDB非主键索引的叶子节点存储的是主键和其他带索引的列数据,因此查询时做到覆盖索引会非常高效。

数据库自增主键可能的问题。

  1. 因为自动增长,在手动要插入指定ID的记录时会显得麻烦,尤其是当系统与其它系统集成时,需要数据导入时,很难保证原系统的ID不发生主键冲突(前提是老系统也是数字型的)。特别是在新系统上线时,新旧系统并行存在,并且是异库异构的数据库的情况下,需要双向同步时,自增主键将是你的噩梦;
  2. 在系统集成或割接时,如果新旧系统主键不同是数字型就会导致修改主键数据类型,这也会导致其它有外键关联的表的修改,后果同样很严重;
  3. 若系统也是数字型的,在导入时,为了区分新老数据,可能想在老数据主键前统一加一个字符标识(例如“o”,old)来表示这是老数据,那么自动增长的数字型又面临一个挑战。
  4. 如果经常有合并表的操作,就可能会出现主键重复的情况
  5. 很难处理分布式存储的数据表。数据量特别大时,会导致查询数据库操作变慢。此时需要进行数据库的水平拆分,划分到不同的数据库中,那么当添加数据时,每个表都会自增长,导致主键冲突

mysql 的内连接、左连接、右连接有什么区别?

    内连接关键字:inner join;左连接: leftjoin;右连接:right join.

    内连接是把匹配的关联数据显示出来 A表中的B公共的数据

    左连接是左边的表全部显示出来。

    右边的表显示出符合条件的数据;右连接正好相反

数据库会死锁吗,举一个死锁的例子,mysql怎么解决死锁。

1一个用户A 访问表A(锁住了表A),然后又访问表B;另一个用户B 访问表B(锁住了表B),然后企图访问表A;这时用户A由于用户B已经锁住表B,它必须等待用户B释放表B才能继续,同样用户B要等用户A释放表A才能继续,这就死锁就产生了。

解决方法

这种死锁比较常见,是由于程序的BUG产生的,除了调整的程序的逻辑没有其它的办法。仔细分析程序的逻辑,对于数据库的多表操作时,尽量按照相同的顺序进 行处理,尽量避免同时锁定两个资源,如操作A和B两张表时,总是按先A后B的顺序处理, 必须同时锁定两个资源时,要保证在任何时刻都应该按照相同的顺序来锁定资源。

2用户A查询一条纪录,然后修改该条纪录;这时用户B修改该条纪录,这时用户A的事务里锁的性质由查询的共享锁企图上升到独占锁,而用户B里的独占锁由于A 有共享锁存在所以必须等A释放掉共享锁,而A由于B的独占锁而无法上升的独占锁也就不可能释放共享锁,于是出现了死锁。这种死锁比较隐蔽,但在稍大点的项 目中经常发生。如在某项目中,页面上的按钮点击后,没有使按钮立刻失效,使得用户会多次快速点击同一按钮,这样同一段代码对数据库同一条记录进行多次操 作,很容易就出现这种死锁的情况。

解决方法:

1、对于按钮等控件,点击后使其立刻失效,不让用户重复点击,避免对同时对同一条记录操作。

2、使用乐观锁进行控制。乐观锁大多是基于数据版本(Version)记录机制实现。即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是 通过为数据库表增加一个“version”字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数 据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。乐观锁机制避免了长事务中的数据 库加锁开销(用户A和用户B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。Hibernate 在其数据访问引擎中内置了乐观锁实现。需要注意的是,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户更新操作不受我们系统的控制,因此可能会造 成脏数据被更新到数据库中。

3、使用悲观锁进行控制。悲观锁大多数情况下依靠数据库的锁机制实现,如Oracle的Select … for update语句,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。如一个金融系统, 当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户账户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读 出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对成百上千个并发,这 样的情况将导致灾难性的后果。所以,采用悲观锁进行控制时一定要考虑清楚。

   如果在事务中执行了一条不满足条件的update语句,则执行全表扫描,把行级锁上升为表级锁,多个这样的事务执行后,就很容易产生死锁和阻塞。类似的情 况还有当表中的数据量非常庞大而索引建的过少或不合适的时候,使得经常发生全表扫描,最终应用系统会越来越慢,最终发生阻塞或死锁。

解决方法:SQL语句中不要使用太复杂的关联多表的查询;使用“执行计划”对SQL语句进行分析,对于有全表扫描的SQL语句,建立相应的索引进行优化。

Innodb和myisam的区别

select for update 是什么含义,会锁表还是锁行或是其他。

mysql进行row lock还是table lock只取决于是否能使用索引(例如主键,unique字段),能则为行锁,否则为表锁未查到数据则无锁。而 使用’<>’,'like’等操作时,索引会失效,自然进行的是表锁。

另外:Myisam 只支持表级锁,InnerDB支持行级锁 添加了(行级锁/表级锁)锁的数据不能被其它事务再锁定,也不被其它事务修改(修改、删除) 。是表级锁时,不管是否查询到记录,都会锁定表。

超大分页的处理的解决方案

数据库层面,这也是我们主要集中关注的(虽然收效没那么大),类似于select * from table where age > 20 limit 1000000,10这种查询其实也是有可以优化的余地的. 这条语句需要load1000000数据然后基本上全部丢弃,只取10条当然比较慢. 当时我们可以修改为select * from table where id in (select id from table where age > 20 limit 1000000,10).这样虽然也load了一百万的数据,但是由于索引覆盖,要查询的所有字段都在索引中,所以速度会很快. 同时如果ID连续的好,我们还可以select * from table where id > 1000000 limit 10,效率也是不错的,优化的可能性有许多种,但核心思想都一样,就是减少load的数据.

从需求的角度减少这种请求…主要是不做类似的需求(直接跳转到几百万页之后的具体某一页.只允许逐页查看或者按照给定的路线走,这样可预测,可缓存)以及防止ID泄漏且连续被人恶意攻击.解决超大分页,其实主要是靠缓存,可预测性的提前查到内容,缓存至redis等k-V数据库中,直接返回即可.

数据中的大表的查询优化操作

1优化shema、sql语句+索引;

2第二加缓存,memcached, redis;

3主从复制,读写分离;

4垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;

5水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key, 为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表;

数据库备份恢复时间

物理备份恢复快,逻辑备份恢复慢

这里跟机器,尤其是硬盘的速率有关系,以下列举几个仅供参考

20G的2分钟(mysqldump)

80G的30分钟(mysqldump)

111G的30分钟(mysqldump)

288G的3小时(xtra)

3T的4小时(xtra)

逻辑导入时间一般是备份时间的5倍以上

为什么 MongoDB 使用B-树

MongoDB 是一种 nosql,也存储在磁盘上,被设计用在 数据模型简单,性能要求高的场合。性能要求高,看看B/B+树的区别第一点:而在 MongoDB 假设的场景中遍历数据并不是常见的需求。MongoDB 这类 nosql 适用于数据模型简单,性能要求高的场合。

B+树内节点不存储数据,所有 data 存储在叶节点导致查询时间复杂度固定为 log n。而B-树查询时间复杂度不固定,与 key 在树中的位置有关,最好为O(1) 我们说过,尽可能少的磁盘 IO 是提高性能的有效手段。MongoDB 是聚合型数据库,而 B-树恰好 key 和 data 域聚合在一起。

为什么 MongoDB 使用B-树

MongoDB 是一种 nosql,也存储在磁盘上,被设计用在 数据模型简单,性能要求高的场合。性能要求高,看看B/B+树的区别第一点:

    B+树内节点不存储数据,所有 data 存储在叶节点导致查询时间复杂度固定为 log n。而B-树查询时间复杂度不固定,与 key 在树中的位置有关,最好为O(1)

我们说过,尽可能少的磁盘 IO 是提高性能的有效手段。MongoDB 是聚合型数据库,而 B-树恰好 key 和 data 域聚合在一起。

你们的数据库单表数据量是多少?一般多大的时候开始出现查询性能急剧下降?

    跟mysql版本有关系,如果是5.7的话,是几千万。但是5.7之后基本上就不用考虑数据量的问题了。但是这个问题问的不好,因为性能急剧下降不但但是跟数据量这一个因素有关系。

    还有机器的配置,比如内存,如果内存放不下索引而把索引放在了虚拟内存上,那么效率就会急剧下降了。还有就是sql建立合适的索引了。

你们数据库是否支持emoji表情,如果不支持,如何操作?

MySQL 数据库建议都提前采用utf8mb4 字符集。

MySQL中如何存储在大图片和大文件:

图片直接以二进制形式存储在数据库中。一般数据库提供一个二进制字段来存储二进制数据。比如mysql中有个blob字段。oracle数据库中是blob或bfile类型。图片存储在磁盘上,数据库字段中保存的是图片的路径

如果有很多数据插入MYSQL 你会选择什么方式?

批量插入、使用事务提交,批量插入数据库、使用优化SQL语句:将SQL语句进行拼接,使用 insert into table () values (),(),(),()然后再一次性插入,如果字符串太长,

分库分表有没有做过?线上的迁移过程是怎么样的?如何确定数据是正确的

数据库集群之主从集群也就是读写分离,也提到了读写分离其实只是分担了访问的压力,但是存储的压力没有解决。当这种数据量达到千万甚至上亿的时候,读写分离就已经满足不了,读写性能下降严重。

分库:分库讲白了就是比如现在你有一个数据库服务器,数据库中有两张表分别是用户表和订单表。如果要分库的话现在你需要买两台机子,搞两个数据库分别放在两台机子上,并且一个数据库放用户表,一个数据库放订单表也就是一台服务器的资源例如CPU、内存、IO、磁盘等是有限的,所以这时候分库分表就上啦!

这样存储压力就分担到两个服务器上了,但是会带来新的问题,所以东西变复杂了都会有新的问题产生。

1、联表查询问题 也就是join了,之前在一个数据库里面可以用上join用一条sql语句就可以联表查询得到想要的结果,但是现在分为多个数据库了,所以join用不上了。就比如现在要查注册时间在2019年之后用户的订单信息,你就需要先去数据库A中用户表查询注册在2019年之后的信息,然后得到用户id,再拿这些id去数据库B订单表中查找订单信息,然后再拼接这些信息返回。所以等于得多写一些代码了。

2、事务问题 搞数据库基本上都离不开事务,但是现在不同的数据库事务就不是以前那个简单的本地事务了,而是分布式事务了,而引入分布式事务也提高了系统的复杂性,并且有些效率不高还会影响性能例如Mysql XA。还有基于消息中间件实现分布式事务的等等这里不展开讲述。

分表:我们已经做了分库了,但是现在情况是我们的表里面的数据太多了,就一不小心你的公司的产品火了,像抖音这种,所有用户如果就存在一张表里吃不消,所以这时候得分表。分别又分垂直分表和水平分表。

垂直分表:垂直分表的意思形象点就像坐标轴的y轴,把x轴切成了两半,对应到我们的表就是比如我们表有10列,现在一刀切下去,分成了两张表,其中一张表3列,另一张表7列。

这个一刀切下去让两个表分别有几列不是固定的,垂直分表适合表中存在不常用并且占用了大量空间的表拆分出去。就拿头条的用户信息,比如用户表只有用户id、昵称、手机号、个人简介这4个字段。但是手机号和个人简介这种信息就属于不太常用的,占用的空间也不小,个人简介有些人写了一坨。所以就把手机号和个人简介这两列拆分出去。那垂直分表影响就是之前只要一个查询的,现在需要两次查询才能拿到分表之前的完整用户表信息。

水平分表:水平分表的意思形象点就像坐标轴的x轴,把y轴切成了两半(当然不仅限于切一刀,可以切好几份)。也拿用户表来说比如现在用户表有5000万行数据,我们切5刀,分成5个表,每个表1000万行数据。水平分表就适合用户表行数很多的情况下,一般单表行数超过5000万就得分表,如果单表的数据比较复杂那可能2000万甚至1000万就得分了,这个得看实际情况有些表很简单可能一亿行都不用分。所以当一个表行数超过千万级别的时候关注一下,如果没有性能问题就可以再等等看,不要急着分表,因为分表会是带来很多问题。水平分表的问题比垂直分表就更烦了

要考虑怎么切,讲的高级点就叫路由

1、按id也就是范围路由,比如id 值1999万的放一张表,1000万1999放一张表,一次类推。这个得试的,因为范围分的大了,可能性能还有问题,范围分的小了。。那表不得多死。

这种分法的好处就是容易切啊,简单粗暴,以后新增的数据分表都不会影响到之前的数据,之前的数据都不需要移动。

2、哈希路由 就是取几列哈希一下看看数据哪个库,比如拿id来做哈希,1500取余8等于4,所以这条记录就放在user_4这个表中,2011取余8等于3,所以这条记录就放在user_3中。这种分法好处就是分的很均匀,基本上每个表的数据都差不多,但是以后新增数据又得分表了咋办,以前的数据都得动,比较烦!

3、搞一张表来存储路由关系 还是拿用户表来说,就是弄一个路由表,里面存userId和表编号,表示这个userId是这张user表的的。这种方式也简单,之后又要分表了之后改改路由表,迁移一部分数据。但是这种方法导致每次查询都得查两次,并且如果路由表太大了,那路由表又成为瓶颈了!

数据库连接池实现原理,数据库连接池的工作机

JDBC操作数据库原理:一般来说,java应用程序访问数据库的过程是:

   ①装载数据库驱动程序;

   ②通过jdbc建立数据库连接;

   ③访问数据库,执行sql语句;

   ④断开数据库连接。

我们可以看出来,“数据库连接”是一种稀缺的资源,为了保障网站的正常使用,应该对其进行妥善管理。其实我们查询完数据库后,如果不关闭连接,而是暂时存放起来,当别人使用时,把这个连接给他们使用。就避免了一次建立数据库连接和断开的操作时间消耗。

猜你喜欢

转载自blog.csdn.net/weixin_41605937/article/details/105993517