数据库

数据库

SQL 之连接查询

外连接:

1) 左连接(左外连接)以左表为基准进行查询,左表数据会全部显示出来, 右表 如果和左表匹配 的数据则显示相应字段的数据,如果不匹配,则显示为NULL;

2) 右连接(右外连接)以右表为基准进行查询,右表数据会全部显示出来, 右表 如果和左表匹配的数据则显示相应字段的数据,如果不匹配,则显示为NULL;

3) 全连接就是先以左表进行左外连接,然后以右表进行右外连接。内连接:显示表之间有连接匹配的所有行。

SQL 之聚合函数

聚合函数是对一组值执行计算并返回单一的值的函数,它经常与 SELECT 语句的 GROUP BY 子句一同使用。

1) .AVG  返回指定组中的平均值,空值被忽略;例:select prd_no,avg(qty) from sales group by prd_no

2) COUNT 返回指定组中 项目的数量。 例:select count(*) from sales; 2). MAX 返回指定数据的最大值;MIN 返回指定数据的最小值;SUM 

回指定数据的和,只能用于数字列,空值被忽略。 例:select prd_no,max(qty) from sales group by prd_no

3)使用 group by 子句对数据进行分组;对 group by 子句形成的组运行 聚集函数计算每一组的值;最后用 having 子句去掉不符合条件的组; having 子 句中的每一个元素也必须出现在 select 列表中。有些数据库例外, 如 oracle. 例 :select prd_no,max(qty) from sales group by prd_no having prd_no>10

SQL 之 SQL 

举例:

select admin from user where username='admin' or 'a'='a' and passwd=''or 'a'='a'

防止 SQL 注入,使用预编译语句是预防 SQL 注入的最佳方式,如select admin from user where username=?And password=?

使用预编译的 SQL 语句语义不会发生改变,在 SQL 语句中,变量用问号? 表示。像上面例子中,username 变量传递的'admin' or 'a'='a' 参数,也只会当  username 字符串来解释查询,从根本上杜绝了 SQL 注入攻击的发生。

注意:使用 mybaits  mapper #方式能够很大程度防止 SQL 注入,

$方式 无法防止 SQL 注入.

SQL Select 语句完整的执行顺序:

查询中用到的关键词主要包含六个,并且他们的顺序依次为select--from--where--group by--having--order by

其中 select 和 from 是必须的,其他关键词是可选的, 这六个关键词的执行顺序如下:

from:需要从哪个数据表检索数据where:过滤表中数据的条件

group by:如何将上面过滤出的数据分组having:对上面已经分组的数据进行过滤的条件select:查看结果集中的哪个列,或列的计算结果order by :按照什么样的顺序来查看返回的数据

存储引擎

  1. 概念

数据库存储引擎是数据库底层软件组织,数据库管理系统(DBMS)使用数据引擎进行创建.查询. 更新和删除数据。不同的存储引擎提供不同的存储机制.

索引技巧.锁定水平等功能,使用不同 的存储引擎,还可以 获得特定的功能。现在许多不同的数据库管理系统都支持多种不同的数据引 擎。

存储引擎主要有: 1. MyIsam , 2. InnoDB, 3. Memory, 4. Archive, 5.

Federated 。

  1. InnoDB

InnoDB 底层存储结构为B+树,B 树的每个节点对应innodb 的一个page, page 大小是固定的, 一般设为 16k。其中非叶子节点只有键值,叶子节点包含完成数据。

                                                  1)经常更新的表,适合处理多重并发的更新请求。

2) 支持事务。

3) 可以从灾难中恢复(通过 bin-log 日志等)。

4) 外键约束。只有他支持外键。            5)支持自动增加列属性 auto_increment。

  1. TokuDB

TokuDB 底层存储结构为 Fractal Tree,Fractal Tree 的结构与 B+树有些

类似, 在 Fractal Tree 中,每一个 child 指针除了需要指向一个 child 节点外, 还会带有一个 Message Buffer ,这个 Message Buffer 是一个 FIFO 的队列, 用来缓存更新操作。

例如,一次插入操作只需要落在某节点的 Message Buffer 就可以马上返回了,并不需要搜索到叶 子节点。这些缓存的更新会在查询时或后台异步合并应用到对应的节点中。

TokuDB 在线添加索引,不影响读写操作, 非常快的写入性能,

Fractal-tree 在事务实现上有优 势。 他主要适用于访问频率不高的数据或历史数据归档。

  1. MyIASM

MyIASM 是 MySQL 默认的引擎,但是它没有提供对数据库事务的支持, 也不支持行级锁和外键, 因此当 INSERT(插入)或 UPDATE(更新)数据时即写操作需要锁定整个表,效率便会低一些。

ISAM 执行读取操作的速度很快,而且不占用大量的内存和存储资源。在设计之初就预想数据组织 成有固定长度的记录,按顺序存储的。---ISAM 是一种静态索引结构。 缺点是它不 支持事务处理。

  1. Memory

Memory(也叫 HEAP)堆内存:使用存在内存中的内容来创建表。每个MEMORY 表只实际对应 一个磁盘文件。MEMORY 类型的表访问非常得快, 因为它的数据是放在内存中的,并且默认使用 HASH 索引。但是一旦服务关闭, 表中的数据就会丢失掉。 Memory 同时支持散列索引和树索 引,树索引可以使用部分查询和通配查询,也可以使用和>=等操作符方便数据挖掘,散列 引相等的比较快但是对于范围的比较慢很多。

索引

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

索引就是加快检索表中数据的方法。数据库的索引类似于书籍的索引。在书

籍中,索引允许用户不必翻阅完整个书就能迅速地找到所需要的信息。在数据库中,索引也允许数据库程序迅速地找到表中的数据,而不必扫描整个数据库。

MySQL 数据库几个基本的索引类型:普通索引.唯一索引.主键索引.全文索引.组合索引

1. 普通索引

是最基本的索引,它没有任何限制。它有以下几种创建方式:

(1) 直接创建索引

CREATE INDEX index_name ON table(column[length]))

(2) 修改表结构的方式添加索引

ALTER TABLE table_name ADD INDEX index_name ON (column[length]))

(3) 创建表的时候同时创建索引

 

 

 

 

 

 

 

 

 

(4) 删除索引

 

DROP INDEX index_name ON table

2. 唯一索引

与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。它有以下几种创建方式:

(1) 创建唯一索引

(2) 修改表结构

(3) 创建表的时候直接指定

3. 主键索引

是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引:

PRIMARY KEY (`id`)

);

4. 组合索引

指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合

5. 全文索引

主要用来查找文本中的关键字,而不是直接与索引中的值相比较。fulltext 索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的 where 语句的参数匹配。fulltext 索引配合 match against 操作使用,而不是一般的 where 语句加 like。它可以在 create table,alter table ,create index 使用,不过目前只有 char.varchar,text 列上可以创建全文索引。值得一提的是,在数据量较大时候,现将数据放入一个没有全局索引的表中,然后再用 CREATE index 创建 fulltext 索引,要比先为一张表建立 fulltext 然后再将数据写入的速度快很多。

(1) 创建表的适合添加全文索引

(2) 修改表结构添加全文索引

(3) 直接创建全文索引

索引的优点

创建唯一性索引,保证数据库表中每一行数据的唯一性

大大加快数据的检索速度,这也是创建索引的最主要的原因

加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排

序的时间。

通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能。

索引的缺点

创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加

索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大

当对表中的数据进行增加.删除和修改的时候,索引也要动态的维护,降低了数据的维护速度

常见索引原则有

选择唯一性索引:唯一性索引的值是唯一的,可以更快速的通过该索引来确定某条记录。

为经常需要排序.分组和联合操作的字段建立索引. 为常作为查询条件的字段建立索引。

限制索引的数目:越多的索引,会使更新表变得很浪费时间。

尽量使用数据量少的索引:如果索引的值很长,那么查询的速度会受到影响。尽量使用前缀来索引:如果索引字段的值很长,最好使用值的前缀来索引。删除不再使用或者很少使用的索引

最左前缀匹配原则,非常重要的原则。

尽量选择区分度高的列作为索引:区分度的公式是表示字段不重复的比例索引列不能参与计算,保持列“干净”:带函数的查询不参与索引。

尽量的扩展索引,不要新建索引。

数据库三范式

范式是具有最小冗余的表结构。3 范式具体如下:

  1. 第一范式(1st NF First Normal Fromate)

第一范式的目标是确保每列的原子性:如果每列都是不可再分的最小数据单元(也称为最小的原子  单元,则满足第一范式(1NF)

第一范式(1NF)要求数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值。

若某一列有多个值,可以将该列单独拆分成一个实体,新实体和原实体间是一对多的关系。

在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。

第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。

第一范式的合理遵循需要根据系统的实际需求来定。比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成一个数据库表的字段就行。但是如果系统经常会访问“地址”属性中的“城市”部分,那么就非要“地址”这个属性重新拆分为省份.城市.详细地址等多个部分进行存储,这样在对地址中某一部分操作的时候将非常方便。这样设计才算满足了数据库的第一范式

  1. 第二范式(2nd NF-Second Normal Fromate)

首先满足第一范式,并且表中非主键列不存在对主键的部分依赖。 第二范式要求每个表只描述一 件事情。

满足第二范式(2NF)必须先满足第一范式(1NF)。

第二范式要求实体中没一行的所有非主属性都必须完全依赖于主键;即:非主属性必须完全依赖于主键。

完全依赖:主键可能由多个属性构成,完全依赖要求不允许存在非主属性依赖于主键中的某一部分属性。

若存在哪个非主属性依赖于主键中的一部分属性,那么要将发生部分依赖的这一组属性单独新建一个实体,并且在旧实体中用外键与新实体关联,并且新实体与旧实体间是一对多的关系。

第二范式在第一范式的基础之上更进一层。第二范式需要确保数据库表中的

每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保 存在同一张数据库表中。

  1. 第三范式(3rd NF Third Normal Fromate)

第三范式定义是,满足第二范式,并且表中的列不存在对非主键列的传递依赖。除了主键订单编号外,顾客姓名依赖于非主键顾客编号。

满足第三范式必须先满足第二范式。

第三范式要求:实体中的属性不能是其他实体中的非主属性。因为这样会出现冗余。即:属性不依赖于其他非主属性。

如果一个实体中出现其他实体的非主属性,可以将这两个实体用外键关联, 而不是将另一张表的非主属性直接写在当前表中。

第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。

数据库事务

1. 事务(TRANSACTION)是作为单个逻辑工作单元执行的一系列操作,这些操作作为一个整体一起向 系统提交,要么都执行.要么都不执行 。事务是一个不可分割的工作逻辑单元 事务必须具备以下四个属性,简称 ACID 属性:原子性(Atomicity):事务是一个完整的操作。事务的各步操作是不可分

的(原子的);要  么都执行,要么都不执行。

一致性(Consistency):当事务完成时,数据必须处于一致状态。

C  隔离性(Isolation):对数据进行修改的所有并发事务是彼此隔离的,这

表明事务必须是独 立的,它不应以任何方式依赖于或影响其他事务。

D  永久性Durability):事务完成后,它对数据库的修改被永久保持,事务日志能够保持事务 的永久性。

2 .事务控制语句:

  • BEGIN  START TRANSACTION 显式地开启一个事务;
  • COMMIT 也可以使用 COMMIT WORK,不过二者是等价的。COMMIT 会提交事务,并使已对数据库进行的所有修改成为永久性的;
  • ROLLBACK 也可以使用 ROLLBACK WORK,不过二者是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;
  • SAVEPOINT identifier,SAVEPOINT 允许在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT;
  • RELEASE SAVEPOINT identifier 删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
    • ROLLBACK TO identifier 把事务回滚到标记点;
    • SET TRANSACTION 用来设置事务的隔离级别。InnoDB 存储引 READ UNCOMMITTED.READ COMMITTED.REPEATABLE READ  SERIALIZABLE 。
  1. MySQL 事务处理主要有两种方法

1)  BEGIN, ROLLBACK, COMMIT 来实现

a) BEGIN 开始一个事务

b) ROLLBACK 事务回滚

c) COMMIT 事务确认

2) 直接用 SET 来改变 MySQL 的自动提交模式:

a) SET AUTOCOMMIT=0 禁止自动提交

b) SET AUTOCOMMIT=1 开启自动提交

  1. 事务的四种隔离级别

1) Read uncommitted

读未提交,顾名思义,就是一个事务可以读取另一个未提交事务的数据。

2) Read committed

读已提交,顾名思义,就是一个事务要等另一个事务提交后才能读取数据。

3) Repeatable read

可重复读,就是在开始读取数据(事务开启)时,不再允许修改操作

4) Serializable 序列化

Serializable 是最高的事务隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读.不可重复读与幻读。但是这种事务隔离级别效率低下,比较耗数据库性能,一般不使用。

MySQL 数据库中,支持上面四种隔离级别,默认的为 Repeatable read (可重复读);而在 Oracle 数据库中,只支持 Serializable (串行化)级别 Read committed (读已提交)这两种级别,其中默认的为 Read committed 级别。

存储过程

一组为了完成特定功能的 SQL 语句集,存储在数据库中,经过第一次编译后再次调用不需要再次 编译,用户通过指定存储过程的名字并给出参数(如果

该存储过程带有参数)来执行它。存储过 程是数据库中的一个重要对象。存储过程优化思路:

  1. 尽量利用一些 SQL 语句来替代一些小循环,例如聚合函数,求平均函数等。
    1. 中间结果存放于临时表,加索引。
    2. 少使用游标。SQL 是个集合语言,对于集合运算具有较高性能。而cursors 是过程运算。比 如对一个 100 万行的数据进行查询。游标需要读表

100 万次,而不使用游标则只需要少量几 次读取。

  1. 事务越短越好。SQLserver 支持并发操作。如果事务过多过长,或者隔离级别过高,都会造 成并发操作的阻塞,死锁。导致查询极慢,cpu 占用率极地。
    1. 使用 try-catch 处理错误异常。6.查找语句尽量不要放在循环内。

10 触发器

触发器是一段能自动执行的程序,是一种特殊的存储过程,触发器和普通的存储过程的区别是: 触发器是当对某一个表进行操作时触发。诸如: update.insert.delete 这些操作的时候,系统 会自动调用执行该表上对应的触发器。SQL Server 2005 中触发器可以分为两类DML 触发器和 DDL 触发器,其中 DDL 触发器它们会影响多种数据定义语言语句而激发,这些语句有create. alter.drop 语句。

11 数据库并发策略

并发控制一般采用三种方法,分别是乐观锁和悲观锁以及时间戳。

乐观锁

乐观锁认为一个用户读数据的时候,别人不会去写自己所读的数据;悲观锁就刚好相反,觉得自 己读数据库的时候,别人可能刚好在写自己刚读的数据, 其实就是持一种比较保守的态度;时间 戳就是不加锁,通过时间戳来控制并发出现的问题。

悲观锁

悲观锁就是在读取数据的时候,为了不让别人修改自己读取的数据,就会先对自己读取的数据加 锁,只有自己把数据读完了,才允许别人修改那部分数据, 或者反过来说,就是自己修改某条数 据的时候,不允许别人读取该数据,只有等自己的整个事务提交了,才释放自己加上的锁,才允 许其他用户访问那部分数据。

两种锁的使用场景

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

乐观锁常见的两种实现方式版本号机制

一般是在数据表中加上一个数据版本号 version 字段,表示数据被修改的次数,当数据被修改时,version 值会加一。当线程要更新数据值时,在读取数据的同时也会读取 version 值,在提交更新时,若刚才读取到的 version 值为当

前数据库中的 version 值相等时才更新,否则重试更新操作,直到更新成功。CAS 算法即 compare and swap(比较与交换),是一种有名的无锁算法。

无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS 算法涉及到三个操作数

需要读写的内存值 V 进行比较的值 A

拟写入的新值 B

当且仅当的值等于CAS 通过原子方式用新值来更新的值, 否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。

乐观锁的缺点

ABA 问 题

如果一个变量初次读取的时候是值,并且在准备赋值的时候检查到它仍然是值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回 A, CAS 操作就会误认为它从来没有被修改过。这个问题被称为 CAS 操作的 "ABA"问题。

JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。

循环时间长开销大

自旋 CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功, 会给 CPU 带来非常大的执行开销。 如果 JVM 能支持处理器提供的 pause 指令那么效率会有一定的提升,pause 指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使 CPU 不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突memory order violation)而引起 CPU 流水线被清空CPU pipeline flush),从而提高 CPU 的执行效率。

只能保证一个共享变量的原子操作

CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5 开始,提供了 AtomicReference 类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用 AtomicReference 类把多个共享变量合并成一个共享变量来操作

CAS 与 synchronized 的使用情景

简单的来说 CAS 适用于写比较少的情况下(多读场景,冲突一般较少), synchronized 适用于写比较多的情况下(多写场景,冲突一般较多)

对于资源竞争较少(线程冲突较轻)的情况,使用 synchronized 同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu 资源; CAS 基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少, 因此可以获得更高的性能。

对于资源竞争严重(线程冲突严重)的情况,CAS 自旋的概率会比较大, 从而浪费更多的 CPU 资源,效率低于 synchronized。

补充: Java 并发编程这个领域中 synchronized 关键字一直都是元老级的

角色,很久之前很多人都会称它为 “重量级锁” 。但是,在 JavaSE 1.6 之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 偏向锁 轻量级锁 以及其它各种优化之后变得在某些情况下并不是那么重了。

synchronized 的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞, 竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较 少的情况下,可以获得和 CAS 类似的性能;而线程冲突严重的情况下,性能远高 CAS

时间戳

时间戳就是在数据库表中单独加一列时间戳,比如“TimeStamp”,每次读出来的时候,把该字 段也读出来,当写回去的时候,把该字段加 1,提交之前 跟数据库的该字段比较一次,如果比数 据库的值大的话,就允许保存,否则不允许保存,这种处理方法虽然不使用数据库系统提供的锁 机制,但是这种方法可以大大提高数据库处理的并发量,

以上悲观锁所说的加“锁”,其实分为几种锁,分别是:排它锁(写锁)和共享锁(读锁)。

12 数据库锁

  1. 行级锁

行级锁是一种排他锁,防止其他事务修改此行;在使用以下语句时,Oracle 会自动应用行级锁:

INSERT.UPDATE.DELETE.SELECT … FOR UPDATE [OF columns] [WAIT n | NOWAIT];

SELECT … FOR UPDATE 语句允许用户一次锁定多条记录进行更新

使用 COMMIT 或 ROLLBACK 语句释放锁。

  1. 表级锁

表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL 引擎支持。最常使 用的 MYISAM  INNODB 都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁 (排他锁)。

  1. 页级锁

页级锁是 MySQL 中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级 冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。BDB 支持页级锁

13 基于 Redis 分布式锁

  1. 获取锁的时候,使用 setnx(ETNX key val:当且仅当 key 不存在时, set 一个 key  val 的字符串,返回 1;若 key 存在,则什么都不做,返回 0) 加锁,锁的 value 值为一个随机生成的 UUID,在释放锁的时候进行判断。并使用 expire 命令为锁添 加一个超时时间,超过该时间则自动释放锁。
  2. 获取锁的时候调用 setnx,如果返回 0,则该锁正在被别人使用,返回 1 则成功获取 锁。 还设置一个获取的超时时间,若超过这个时间则放弃获取锁。
  3. 释放锁的时候,通过 UUID 判断是不是该锁,若是该锁,则执行 delete 进行锁释放。

14 分区分表

分库分表有垂直切分和水平切分两种。

▪ 垂直切分:将表按照功能模块.关系密切程度划分出来,部署到不同的库上。例如,我们会 建立定义数据库 workDB.商品数据库 payDB.用户数据库

userDB.日志数据库 logDB 等,分别用于存储项目数据定义表.商品定义表.用户数据表.日志数据表等。

▪ 水平切分:当一个表中的数据量过大时,我们可以把该表的数据按照某种规则,例如 userID 散列,进行划分,然后存储到多个结构相同的表,和不同的库上。例如,我们的 userDB 中的用户数据表中,每一个表的数据量都很大, 就可以把 userDB 切分为结构相同的多个 userDB:part0DB.part1DB 等,再将 userDB 上 的 用 户 数 据 表 userTable,  userTable: userTable0.userTable1 等,然后将这些表按照一定的规则存储到多个 userDB 上。

15 应该使用哪一种方式来实施数据库分库分表,这要看

数据库中数据量的瓶颈 所在,并综合项目的业务类型进行考虑。

如果数据库是因为表太多而造成海量数据,并且项目的各项业务逻辑划分清. 低耦合,那么规则简单明了.容易实施的垂直切分必是首选。

而如果数据库中的表并不多,但单表的数据量很大.或数据热度很高,这种情况 之下就应该选择水平切分,水平切分比垂直切分要复杂一些,它将原本逻辑上属 于一体的数据进行了物理分割,除了在分割时要对分割的粒度做好评估, 考虑数 据平均和负载平均,后期也将对项目人员及应用程序产生额外的数据管理负担。 在现实项目中,往往是这两种情况兼而有之,这就需要做出权衡,甚至既需要垂 直切分,又需要水平切分。我们的游戏项目便综合使用了垂直与水平切分,我们 首先对数据库进行垂直切分,然后,再针对一部分表,通常是用户数据表,进行 水平切分。

单库多表

随着用户数量的增加,user 表的数据量会越来越大,当数据量达到一定 度的时候对 user 表的查询会渐渐的变慢,从而影响整个 DB 的性能。如果使 MySQL, 还有一个更严重的问题是,当需要添加一列的时候,MySQL 会锁表, 期间所有的读写操作只能等待。

可以将 user 进行水平的切分,产生两个表结构完全一样的user_0000,user_0001 等表,user_0000 + user_0001 + …的数据刚好是一份完整的数据。

多库多表

随着数据量增加也许单台 DB 的存储空间不够,随着查询量的增加单台数据 库服务器已经没办法支撑。这个时候可以再对数据库进行水平区分。

分库分表规则举例: 通过分库分表规则查找到对应的表和库的过程。如分库分表的规则是 user_id 除以的方式,当用户新注册了一个账号,账号 id 123,我们可以通过 id 除以的方式确定此账号应该保存到 User_0003 表中。当用户 123 登录的时 候,我们通过 123 除以后确定记录在User_0003 

16 MySQL 读写分离

在实际的应用中,绝大部分情况都是读远大于写。MySQL 提供了读写分离的机制,所有的写操作都必须对应到 Master,读操作可以在 Master 和 Slave 机 器上进行,Slave 与 Master 的结构完全一样,一个 Master 可以有多个Slave,甚 至 Slave 下还可以挂 Slave,通过此方式可以有效的提高 DB 集群的每秒查询率. 所有的写操作都是先在 Master 上操作,然后同步更新到 Slave

上,所以 Master 同步到 Slave 机器有一定的延迟,当系统很繁忙的时候, 延迟问题会 更加严重,Slave 机器数量的增加也会使这个问题更加严重。

此外,可以看出 Master 是集群的瓶颈,当写操作过多,会严重影响到Master 的 稳定性,如果 Master 挂掉,整个集群都将不能正常工作。 所以,

1. 当读压力很大的时候,可以考虑添加 Slave 机器的分式解决,但是当 Slave 机器达到一定的数量就得考虑分库了。 2. 当写压力很大的时候,就必须 得进行分库操作。

17 MySQL 常用 30  SQL 查询语句优化方法

  1. 应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。
  2. 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where  order by 涉及的列上建立索引。
  3. 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where num is null

可以在 num 上设置默认值 0,确保表中 num 列没有 null 值,然后这样查询:

select id from t where num=0

  1. 尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如:

select id from t where num=10 or num=20 可以这样查询:

select id from t where num=10 union all

select id from t where num=20

  1. 下面的查询也将导致全表扫描:(不能前置百分号) select id from t where name like ‘%c%’

下面走索引

select id from t where name like ‘c%’ 若要提高效率,可以考虑全文检索。

  1. in  not in 也要慎用,否则会导致全表扫描,如: select id from t where num in(1,2,3)

对于连续的数值,能用 between 就不要用 in 了:select id from t where num between 1 and 3

  1. 如果在 where 子句中使用参数,也会导致全表扫描。因为 SQL 只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:

select id from t where num=@num 可以改为强制查询使用索引:

select id from t with(index(索引名)) where num=@num

  1. 应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where num/2=100

应改为:

select id from t where num=100*2

  1. 应尽量避免在 where 子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where substring(name,1,3)=’abc’ –name 以 abc 开头的 id

select id from t where datediff(day,createdate,’2005-11-30′)=0 –’ 2005-11-30′生成的 id

应改为:

select id from t where name like ‘abc%’

select id from t where createdate>=’2005-11-30′ and createdate<’ 2005-12-1′

  1. 不要在 where 子句中的“=”左边进行函数.算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
  2. 在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使 用,并且应尽可能的让字段顺序与索引顺序相一致。
    1. 不要写一些没有意义的查询,如需要生成一个空表结构: select col1,col2 into #t from t where 1=0

这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样: create table #t(…)

  1. 很多时候用 exists 代替 in 是一个好的选择:

select num from a where num in(select num from b) 用下面的语句替换:

select num from a where exists(select 1 from b where num=a.num) 14.并不是所有索引对查询都有效,SQL 是根据表中数据来进行查询优化的,

当索引列有大量数据重复时,SQL 查询可能不会去利用索引,如一表中有字段sex,male.female 几乎各一半,那么即使在 sex 上建了索引也对查询效率起不了作用。

  1. 索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert  update 的效率,因为 insert  update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数较好不要超过个,若太多则应考虑一些不常使用到的列上建的索引是否有 必要。
  2. 应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。
  3. 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型, 这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会 逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
  4. 尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
  5. 任何地方都不要使用  select * from t  ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
  6. 尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。
    1. 避免频繁创建和删除临时表,以减少系统表资源的消耗。
    2. 临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如, 当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,较好使 用导出表。
    3. 在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log ,以提高速度;如果数据量不大, 为了缓和系统表的资源,应先 create table,然后 insert。
    4. 如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除, truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。
    5. 尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过 1 万行,那么就应该考虑改写。
    6. 使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。
    7. 与临时表一样,游标并不是不可使用。对小型数据集使用FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时 间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。
    8. 在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONEINPROC 消息。
    9. 尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
      1. 尽量避免大事务操作,提高系统并发能力。
 

18 数据库优化方案整理

1.优化说明

(1) 有数据表明,用户可以承受的最大等待时间为秒。数据库优化策略有很多,设计初期,建立好的数据结构对于后期性能优化至关重要。因为数据库结构是系统的基石,基础打不好,使用各种优化策略,也不能达到很完美的效果。

(2) 

 
   

数据库优化的几个方面

可以看出来,数据结构.SQL.索引是成本最低,且效果最好的优化手段。(3)性能优化是无止境的,当性能可以满足需求时即可,不要过度优化。2.优化方向

 

(1) SQL 以及索引的优化

首先要根据需求写出结构良好的 SQL,然后根据 SQL 在表中建立有效的索引。但是如果索引太多,不但会影响写入的效率,对查询也有一定的影响。

(2) 合理的数据库是设计

根据数据库三范式来进行表结构的设计。设计表结构时,就需要考虑如何设计才能更有效的查询。

数据库三范式:

第一范式:数据表中每个字段都必须是不可拆分的最小单元,也就是确保每一列的原子性;

第二范式:满足一范式后,表中每一列必须有唯一性,都必须依赖于主键; 第三范式:满足二范式后,表中的每一列只与主键直接相关而不是间接相关

(外键也是直接相关),字段没有冗余。

注意:没有最好的设计,只有最合适的设计,所以不要过分注重理论。三范式可以作为一个基本依据,不要生搬硬套。

有时候可以根据场景合理地反规范化: A:分割表。

B:保留冗余字段。当两个或多个表在查询中经常需要连接时,可以在其中一个表上增加若干冗余的字段,以 避免表之间的连接过于频繁,一般在冗余列的数据不经常变动的情况下使用。

C:增加派生列。派生列是由表中的其它多个列的计算所得,增加派生列可以减少统计运算,在数据汇总时可以大大缩短运算时间。

数据库五大约束:

A:PRIMARY key: 设 置 主 键 约 束 ; B:UNIQUE:设置唯一性约束,不能有重复值; C:DEFAULT 默认值约束

D:NOT NULL:设置非空约束,该字段不能为空; E:FOREIGN key :设置外键约束。

字段类型选择:

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

B:VARCHAR 的长度只分配真正需要的空间C:使用枚举或整数代替字符串类型        D:尽量使用 TIMESTAMP 而非 DATETIME

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

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

(3) 系统配置的优化

例如:MySQL 数据库 my.cnf

(4) 硬件优化

更快的 IO.更多的内存。一般来说内存越大,对于数据库的操作越好。但是CPU 多就不一定了,因为他并不会用到太多的 CPU 数量,有很多的查询都是单CPU。另外使用高的 IO(SSD.RAID),但是 IO 并不能减少数据库锁的机制。所以说如果查询缓慢是因为数据库内部的一些锁引起的,那么硬件优化就没有什么意义。

3. 优化方案代码优化

之所以把代码放到第一位,是因为这一点最容易引起技术人员的忽视。很多技术人员拿到一个性能优化的需求以后,言必称缓存.异步.JVM 等。实际上,第一步就应该是分析相关的代码,找出相应的瓶颈,再来考虑具体的优化策略。有一些性能问题,完全是由于代码写的不合理,通过直接修改一下代码就能解决问题的,比如 for 循环次数过多.作了很多无谓的条件判断.相同逻辑重复多次等。

举个例子:

一个 update 操作,先查询出 entity,再执行 update,这样无疑多了一次数据库交互。还有一个问题,update 语句可能会操作一些无需更新的字段。

我们可以将表单中涉及到的属性,以及 updateTime,updateUser 等赋值到 entity,直接通过 pdateByPrimaryKeySelective,去 update 特定字段

定位慢 SQL,并优化

这是最常用.每一个技术人员都应该掌握基本的 SQL 调优手段(包括方法. 工具.辅助系统等)。这里以 MySQL 为例,最常见的方式是,由自带的慢查询日志或者开源的慢查询系统定位到具体的出问题的 SQL,然后使用 explain.profile等工具来逐步调优,最后经过测试达到效果后上线。

SqlServer 执行计划:

通过执行计划,我们能得到哪些信息: A:哪些步骤花费的成本比较高

B:哪些步骤产生的数据量多,数据量的多少用线条的粗细表示,很直观

C:每一步执行了什么动作

具体优化手段:

A:尽量少用(或者不用)sqlserver 自带的函数

select id from t where substring(name,1,3) = ’abc’

select id from t where datediff(day,createdate,’2005-11-30′) = 0 可以这样查询:

select id from t where name like ‘abc%’

select id from t where createdate >= ‘2005-11-30’ and createdate

< ‘2005-12-1’

B:连续数值条件,用 BETWEEN 不用 IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5

C:Update 语句,如果只更改 1.2 个字段,不要 Update 全部字段,否则频繁调用会引起明显的性能消耗

D:尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型E:不建议使用  select * from t  ,用具体的字段列表代替“*”,不要返回

用不到的任何字段。尽量避免向客户 端返回大数据量,若数据量过大,应该考虑相应需求是否合理

F:表与表之间通过一个冗余字段来关联,要比直接使用 JOIN 有更好的性

G:select count(*) from table;这样不带任何条件的 count 会引起全表

扫描

连接池调优

我们的应用为了实现数据库连接的高效获取.对数据库连接的限流等目的, 通常会采用连接池类的方案,即每一个应用节点都管理了一个到各个数据库的连接池。随着业务访问量或者数据量的增长,原有的连接池参数可能不能很好地满足需求,这个时候就需要结合当前使用连接池的原理.具体的连接池监控数据和当前的业务量作一个综合的判断,通过反复的几次调试得到最终的调优参数。

合理使用索引

索引一般情况下都是高效的。但是由于索引是以空间换时间的一种策略,索引本身在提高查询效率的同时会影响插入.更新.删除的效率,频繁写的表不宜建索引。

选择合适的索引列,选择在 where,group by,order by,on 从句中出现的列作为索引项,对于离散度不大的列没有必要创建索引。

主键已经是索引了,所以 primay key 的主键不用再设置 unique 唯一索引索引类型

主键索引 PRIMARY KEY) 唯一索引 (UNIQUE)

普通索引 INDEX) 组合索引 (INDEX)

全文索引 FULLTEXT) 可以应用索引的操作符 大于等于

Between IN

LIKE 不 以 % 开 头

不能应用索引的操作符NOT IN

LIKE %_ 开 头

如何选择索引字段                                                                A:字段出现在查询条件中,并且查询条件可以使用索引        B:通常对数字的索引和检索要比对字符串的索引和检索效率更高C:语句执行频率高,一天会有几千次以上                            D:通过字段条件可筛选的记录集很小

无效索引

A:尽量不要在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描

B:应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。

C:应尽量避免在 where 子句中使用 or 来连接条件,如果一个字段有索引,一个字段没有索引,将导致引擎放弃使用索引而进行全表扫描

select id from t where num=10 or Name = ‘admin’ 可以这样查询:

select id from t where num = 10 union

select id from t where Name = ‘admin’

union all 返回所有数据,不管是不是重复。 union 会自动压缩,去除重复数据。

D:不做列运算

where age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数.计算表达式等

E:查询 like,如果是 ‘%aaa’ 不会使用到索引

分表

分表方式

水平分割(按行).垂直分割(按列)

分表场景

A: 根据经验,MySQL 表数据一般达到百万级别,查询效率就会很低。B: 一张表的某些字段值比较大并且很少使用。可以将这些字段隔离成单

独一张表,通过外键关联,例如考试成绩,我们通常关注分数,不关注考试详情。水平分表策略

按时间分表:当数据有很强的实效性,例如微博的数据,可以按月分割。按区间分表:例如用户表到一百万用一张表,一百万到两百万用一张表。hash 分表:通过一个原始目标 id 或者是名称按照一定的 hash 算法计算出

数据存储的表名。

读写分离

当一台服务器不能满足需求时,采用读写分离【写: update/delete/add】的方式进行集群。

一台数据库支持最大连接数是有限的,如果用户的并发访问很多,一台服务器无法满足需求,可以集群处理。MySQL 集群处理技术最常用的就是读写分离。

主从同步:数据库最终会把数据持久化到磁盘,集群必须确保每个数据库服务器的数据是一致的。从库读主库写,从库从主库上同步数据。

读写分离:使用负载均衡实现,写操作都往主库上写,读操作往从服务器上读。

缓存

缓存分类

本地缓存:HashMap/ConcurrentHashMap.Ehcache.Guava Cache 等缓存服务:Redis/Tair/Memcache 等

使用场景

短时间内相同数据重复查询多次且数据更新不频繁,这个时候可以选择先从缓存查询,查询不到再从数据库加载并回设到缓存的方式。此种场景较适合用单机缓存。

高并发查询热点数据,后端数据库不堪重负,可以用缓存来扛。

缓存作用:减轻数据库的压力,减少访问时间。

缓存选择:如果数据量小,并且不会频繁地增长又清空(这会导致频繁地垃圾回收),那么可以选择本地缓存。具体的话,如果需要一些策略的支持(比如缓存满的逐出策略),可以考虑 Ehcache;如不需要,可以考虑 HashMap;如需要考虑多线程并发的场景,可以考虑 ConcurentHashMap。

其他情况,可以考虑缓存服务。目前从资源的投入度.可运维性.是否能动态扩容以及配套设施来考虑,我们优先考虑 Tair。除非目前 Tair 还不能支持的场合

(比如分布式锁.Hash 类型的 value),我们考虑用 Redis。

缓存穿透一般的缓存系统,都是按照 key 去缓存查询,如果不存在对应的

value,就应该去后端系统查找(比如 DB)。如果 key 对应的 value 是一定不存在的,并且对该 key 并发请求量很大,就会对后端系统造 成很大的压力。这就叫做缓存穿透。

对查询结果为空的情况也进行缓存,缓存时间设置短点,或者该 key 对应的数据 insert 了之后清理缓存。

缓存并发有时候如果网站并发访问高,一个缓存如果失效,可能出现多个进程同时查询 DB,同时设置缓存的情况,

如果并发确实很大,这也可能造成 DB 压力过大,还有缓存频繁更新的问题。对缓存查询加锁,如果 KEY 不存在,就加锁,然后查 DB 入缓存,然后解

锁;其他进程如果发现有锁就等待,然后等解锁后返回数据或者进入 DB 查询。

缓存雪崩(失效)

当缓存服务器重启或者大量缓存集中在某一个时间段失效,这样在失效的时候,也会给后端系统(比如 DB)

带来很大压力。

不同的 key,设置不同的过期时间,让缓存失效的时间点尽量均匀.

防止缓存空间不够用

(1) 给缓存服务,选择合适的缓存逐出算法,比如最常见的 LRU。

(2) 针对当前设置的容量,设置适当的警戒值,比如 10G 的缓存,当缓存数据达到 8G 的时候,就开始发出报警,提前排查问题或者扩容。

(3) 给一些没有必要长期保存的 key,尽量设置过期时间。

我们看下图,在 WebServe(r  Dao  DB 之间加一层 cache,这层 cache

一般选取的介质是内存,因为我们都知道存入数据库的数据都具有持久化的特点,

 
   

那么读写会有磁盘 IO 的操作,内存的读写速度远比磁盘快得多。(选用存储介质,提高访问速度:内存>>磁盘;减少磁盘 IO 的操作,减少重复查询,提高吞吐量)

常用开源的缓存工具有:ehcache.memcache.Redis。

ehcache  是一个纯 Java 的进程内缓存框架,hibernate 使用其做二级缓存。同时,ehcache 可以通过多播的方式实现集群。本人主要用于本地的缓存,数据库上层的缓存。

memcache 是一套分布式的高速缓存系统,提供 key-value 这样简单的数据储存,可充分利用 CPU 多核,无持久化功能。在做 web 集群中可以用做session 共享,页面对象缓存。

Redis 高性能的 key-value 系统,提供丰富的数据类型,单核 CPU 有抗并发能力,有持久化和主从复制的功能。本人主要使用 Redis  Redis sentinel, 根据不同业务分为多组。

Redis 注意事项

A:在增加 key 的时候尽量设置过期时间,不然 Redis Server 的内存使用会达到系统物理内存的最大值,导致 Redis 使用 VM 降低系统性能;

B:Redis Key 设计时应该尽可能短,Value 尽量不要使用复杂对象;

C:将对象转换成 JSON 对象利用现成的 JSON 后存入 Redis; D:将对象转换成 Google 开源二进制协议对象Google Protobuf,和 JSON 数据格式类似,但是因为是二进制表现,所以性能效率以及空间占用都比

JSON  要小;缺点是  Protobuf  的学习曲线比  JSON  大得多); E:Redis 使用完以后一定要释放连接。

读取缓存中是否有相关数据,如果缓存中有相关数据,则直接返回,这就是所谓的数据命中“hit”

如果缓存中没有相关数据,则从数据库读取相关数据,放入缓存中,再返回。这就是所谓的数据未命中“miss”

缓存的命中率 = 命中缓存请求个数/总缓存访问请求个数 = hit/(hit+miss)

NoSQL

与缓存的区别

先说明一下,这里介绍的和缓存不一样,虽然 Redis 等也可以用来做数据存储方案(比如 Redis 或者 Tair),但 NoSql 是把它作为 DB 来用。如果当作 DB来用,需要有效保证数据存储方案的可用性.可靠性。

使用场景

需要结合具体的业务场景,看这块业务涉及的数据是否适合用 NoSQL 来存储,对数据的操作方式是否适合用 NoSQL 的方式来操作,或者是否需要用到NoSQL 的一些额外特性(比如原子加减等)。

如果业务数据不需要和其他数据作关联,不需要事务或者外键之类的支持, 而且有可能写入会异常频繁,这个时候就比较适合用 NoSQL(比如 HBase)

比如,美团点评内部有一个对 exception 做的监控系统,如果在应用系统发生严重故障的时候,可能会短时间产生大量 exception 数据,这个时候如果选用 MySQL,会造成 MySQL 的瞬间写压力飙升,容易导致 MySQL 服务器的性能急剧恶化以及主从同步延迟之类的问题,这种场景就比较适合用 Hbase 似的 NoSQL 来存储。

视图/存储过程

普通业务逻辑尽量不要使用存储过程,定时任务或报表统计函数可以根据团队资源情况采用存储过程处理。

GVM 调优

通过监控系统(如没有现成的系统,自己做一个简单的上报监控的系统也很容易)上对一些机器关键指标(gc time.gc count.各个分代的内存大小变化.机器的 Load 值与 CPU 使用率.JVM 的线程数等的监控报警,也可以看 gc log jstat 等命令的输出,再结合线上 JVM 进程服务的一些关键接口的性能数据和请求体验,基本上就能定位出当前的 JVM 是否有问题,以及是否需要调优。

异步/多线程

针对某些客户端的请求,在服务端可能需要针对这些请求做一些附属的事情, 这些事情其实用户并不关心或者用户不需要立即拿到这些事情的处理结果,这种 情况就比较适合用异步的方式处理这些事情。

异步作用

A:缩短接口响应时间,使用户的请求快速返回,用户体验更好。

B:避免线程长时间处于运行状态,这样会引起服务线程池的可用线程长时间不够用,进而引起线程池任务队列长度增大,从而阻塞更多请求任务,使得更

多请求得不到技术处理。

C:线程长时间处于运行状态,可能还会引起系统 Load.CPU 使用率.机器整体性能下降等一系列问题,甚至引发雪崩。异步的思路可以在不增加机器数和CPU 数的情况下,有效解决这个问题。

异步实现

A:额外开辟线程,这里可以采用额外开辟一个线程或者使用线程池的做法, IO 线程(处理请求响应)之外的线程来处理相应的任务,在 IO 线程中让response 先返回。

B:使用消息队列(MQ)中间件服务

搜索引擎

例如:solr,elasticsearch

相信自己,坚信自己的目标,去承受常人承受不了的磨难与挫折,不断的努力去奋斗,最终的成功就会是你
原文地址:https://www.cnblogs.com/Valuez/p/13302221.html