数据库表基础

数据库基础
数据
数据(Data)是数据库中存储的基本单元。是一种描述事物的符号。
例如数字、文字、图像、视频等等信息,都可以称为数据。

数据库
数据库,简而言之可视为电子化的文件柜——存储电子文件的处所,用户可以对文件中的数据运行新增、截取、更新、删除等操作。

数据库管理系统
数据库管理系统(Database Management System,简称DBMS)是为管理数据库而设计的电脑软件系统,一般具有存储、截取、安全保障、备份等基础功能

   数据库管理系统主要分为以下两类:

关系数据库
关系数据库是创建在关系模型基础上的数据库,借助于集合代数等数学概念和方法来处理数据库中的数据。现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示。

   几乎所有的数据库管理系统都配备了一个开放式数据库连接(ODBC)驱动程序,令各个数据库之间得以互相集成。

   典型代表有:MySql、Oracle、DB2、Microsoft SQL Server、PostgreSQL等

非关系型数据库
非关系型数据库是对不同于传统的关系数据库的数据库管理系统的统称。与关系数据库最大的不同点是不使用SQL作为查询语言。

   典型代表有:Cassandra、MongoDB、redis、HBase等


关系型数据库的基本存储结构。

表是二维数据结构,有行和列
行(Row)是横排数据,也叫记录(Recond)
列(Column)是竖排数据,也叫字段(Field)
行与列的交叉点是 字段值
表与表之间也存在关系

范式
设计数据库时,为了设计出合理的关系型数据库,所遵循的不同的规范要求

相关概念

函数依赖
B依赖于A:通过A属性(属性组)的值,可以唯一确定B属性的值
例如:学号->姓名,(学号,课程名称)->成绩

完全函数依赖
B完全依赖于A:A是一个属性组,A属性组中所有的属性值才能唯一确定B属性的值
例如:(学号,课程名称)->成绩

部分函数依赖
B部分依赖于A:A是一个属性组,A属性组中部分属性值就能唯一确定B属性的值
例如:(学号,课程名称)->姓名

传递函数依赖
C传递依赖于A:C依赖于B,B依赖于A
例如:学号->专业->辅导员


被其他所有属性完全依赖的属性(属性组)
主属性:码属性组的所有属性
非主属性:除了码属性组中属性的其他属性
分类

第一范式(1NF)每一列都是不可分割的原子数据项
第二范式(2NF)在1NF的基础上,非主属性必须完全依赖于主属性(在1NF的基础上,消除非主属性对主属性的部分函数依赖)
第三范式(3NF)在2NF的基础上,任何非主属性不依赖于其他非主属性(在2NF的基础上,消除传递依赖)
巴斯-科德范式(BCNF)
第四范式(4NF)
第五范式(5NF,又称完美范式)

索引
索引是对数据库表中一个或多个列的值进行排序的结构,主要目的就是加快检索表中数据,亦即能协助信息搜索者尽快的找到符合限制条件的记录ID的辅助数据结构,索引运用在表中某个些字段上,但存储时,独立于表之外

索引存储了指向表中某一行的指针
如果我们在索引里找到某一条记录作为索引的列的值,如何才能找到这一条记录的其它值呢?这是很简单 - 数据库索引同时存储了指向表中的相应行的指针。指针是指一块内存区域, 该内存区域记录的是对硬盘上记录的相应行的数据的引用。因此,索引中除了存储列的值,还存储着一个指向在行数据的索引。也就是说,索引中的Employee_Name这列的某个值(或者节点)可以描述为 (“Jesus”, 0x82829), 0x82829 就是包含 “Jesus”那行数据在硬盘上的地址。如果没有这个引用,你就只能访问到一个单独的值(“Jesus”),而这样没有意义,因为你不能获取这一行记录的employee的其他值-例如地址(address)和年龄(age)。

Mysql索引分类:https://juejin.cn/post/6844904101113757709

索引特点
索引一旦建立,Oracle管理系统会对其进行自动维护, 而且由Oracle管理系统决定何时使用索引
用户不用在查询语句中指定使用哪个索引
在定义primary key或unique约束后系统自动在相应的列上创建索引
用户也能按自己的需求,对指定单个字段或多个字段,添加索引
什么时候创建索引

表经常进行 SELECT 操作
表很大(记录超多),记录内容分布范围很广
列名经常在 WHERE 子句或连接条件中出现
索引优缺点:

索引加快数据库的检索速度
索引降低了插入、删除、修改等维护任务的速度(虽然索引可以提高查询速度,但是它们也会导致数据库系统更新数据的性能下降,因为大部分数据更新需要同时更新索引),即表经常进行INSERT/UPDATE/DELETE操作就不要建议索引了
唯一索引可以确保每一行数据的唯一性,通过使用索引,可以在查询的过程中使用优化隐藏器,提高系统的性能
索引需要占物理和数据空间
深入理解索引可参考:

kb.cnblogs.com/page/45712/
数据库两大神器【索引和锁】 (juejin.cn)

约束(https://juejin.cn/post/6959197870473871374)
约束是对表中的数据进行限定,保证数据的正确性、有效性和完整性,常见约束有:

非空约束NOT NULL: 用于控制字段的内容一定不能为空(NULL)。
创建表时添加约束

CREATE TABLE stu(
id INT,
NAME VARCHAR(20) NOT NULL – name不能为null
);

创建表后添加约束:ALTER TABLE stu MODIFY NAME VARCHAR(20) NOT NULL;

删除约束:ALTER TABLE stu MODIFY NAME VARCHAR(20);

唯一约束UNIQUE: 控件字段内容不能重复,一个表允许有多个 Unique 约束。
受约束的列最多只能有一条记录为null

创建表时添加约束

CREATE TABLE stu(
id INT,
phone_number VARCHAR(20) UNIQUE – 手机号
);
创建表后添加约束:ALTER TABLE stu MODIFY phone_number VARCHAR(20) UNIQUE;

删除唯一约束:ALTER TABLE stu DROP INDEX phone_number;

主键约束PRIMARY KEY: 也是用于控件字段内容不能重复,但它在一个表只允许出现一个
注意

主键非空且唯一

主键就是记录的唯一标识

一张表只能有一个字段为主键

创建表时添加约束

create table stu(
id int primary key,-- 给id添加主键约束
name varchar(20)
);
创建表后添加约束:ALTER TABLE stu MODIFY id INT PRIMARY KEY;

删除主键约束

(错误)alter table stu modify id int;

(正确)ALTER TABLE stu DROP PRIMARY KEY;

自动增长

概念:如果某一列的数据类型为数值类型,可以使用auto_increment设置值的自动增长

创建表时,添加主键约束,并且设置自动增长

create table stu(
id int primary key auto_increment,-- 给id添加主键约束
name varchar(20)
);

创建表后,设置自动增长:ALTER TABLE stu MODIFY id INT AUTO_INCREMENT;

删除自动增长:ALTER TABLE stu MODIFY id INT;

外键约束FOREIGN KEY: 用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。
创建表时添加外键:

create table 表名(

外键列名 数据类型,
constraint 外键名称 foreign key (外键列名) references 主表名称(主表列名)
);
删除外键:alter table 表名 drop foreign key 外键名称;

创建表后添加外键:alter table 表名 add constraint 外键名称 foreign key (外键列名) references 主表名称(主表列名);

级联操作

添加级联:alter table 表名 add constraint 外键名称 foreign key (外键列名) references 主表名称(主表列名) on update cascade on delete cascade;

级联分类

级联更新:on update cascade

级联删除:on delete cascade

外键可以为null,但是不可以为不存在的外键值

检查CHECK: 用于控制字段的值范围。

视图
视图是一种基于数据表的一种虚表

视图是一种虚表
视图建立在已有表的基础上, 视图赖以建立的这些表称为基表
视图向用户提供基表数据的另一种表现形式
视图没有存储真正的数据,真正的数据还是存储在基表中
程序员虽然操作的是视图,但最终视图还会转成操作基表
一个基表可以有0个或多个视图
有的时候,我们可能只关心一张数据表中的某些字段,那么把全部的字段都都显示给他们看,这是不合理的,这时就可以采用视图方式一方面就能够让他们只关注自己的数据,另一方面,我们也保证数据表一些保密的数据不会泄露出来…

我们在查询数据的时候,常常需要编写非常长的SQL语句,几乎每次都要写很长很长…上面已经说了,视图就是基于查询的一种虚表,也就是说,视图可以将查询出来的数据进行封装。。。那么我们在使用的时候就会变得非常方便…

值得注意的是:使用视图可以让我们专注于逻辑,但不提高查询效率

存储过程和函数
存储过程和函数是事先经过编译并存储在数据库中的一段 SQL 语句的集合,调用存储过程和函数可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的。
​ 存储过程和函数的区别在于函数必须有返回值,而存储过程没有。

存储过程的优点:

能够将代码封装起来
保存在数据库之中
让编程语言进行调用
存储过程是一个预编译的代码块,执行效率比较高
一个存储过程替代大量T_SQL语句 ,可以降低网络通信量,提高通信速率
存储过程的缺点:

每个数据库的存储过程语法几乎都不一样,十分难以维护(不通用)
业务逻辑放在数据库上,难以迭代
函数可以区分为自定义函数和内置函数(如:时间日期函数、字符串函数、数学计算相关函数、聚合函数等)

事务
事务是一组原子性的SQL查询,或者说是一个独立的工作单元。如果数据库引擎能够成功的对数据库应用该组查询的全部语句,那么就执行该组查询,如果其中的任何一条语句因为崩溃或者其他原因无法执行,那么所有的语句都不会执行。

1、操作

开启事务:start transaction
回滚:rollback
提交:commit
2、提交事务的方式

自动提交(MySQL):执行一条DML语句会自动提交一次事务
手动提交(Oracle):需要先开始事务,再提交
查看事务的提交方式:select @@autocommit;
1:表示自动提交(默认)
0:表示手动提交
修改事务的提交方式:set @@autocommit = 0;

3、ACID — 数据库事务正确执行的四个基本要素

原子性(Atomicity):一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于事务来说,不可以只执行其中的一部分操作
一致性(Consistency):数据库总是从一个一致性的状态转换到另外一个一致性的状态。
隔离性(Isolation):通常来说,一个事务所做的修改在最终提交之前,对其他事务是不可见的。
持久性(Durability):一旦事务提交,则其所做的修改就会永久保存到数据库中。此时,即使系统崩溃,修改的数据也不会丢失。
一个支持事务(Transaction)中的数据库系统,必需要具有这四种特性,否则在事务过程(Transaction processing)当中无法保证数据的正确性,交易过程极可能达不到交易。

举个例子:A向B转账,转账这个流程中如果出现问题,事务可以让数据恢复成原来一样【A账户的钱没变,B账户的钱也没变】。

一个实现了ACID的数据库,相比没有实现ACID的数据库,通常会需要更强的CPU处理能力、更大的内存和更多的磁盘空间。

4、事务隔离级别

SQL标准中定义了四种隔离级别,每一个级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。较低级别的隔离,通常可以执行更高的并发,系统的开销也更低。

数据库事务的隔离级别由低到高分别为Read uncommitted 、Read committed 、Repeatable read 、Serializable

Read uncommitted (未提交读) 一个事物可以读取另一个未提交事物的数据,容易出现【脏读】。从性能上讲,该种隔离级别不会比其他级别好太多,但确缺乏其他级别的很多好处,除非真的有必要,在实际应用中一般很少使用。
Read committed(提交读) 一个事物要等另一个事物提交后才能读取数据,解读了脏读问题, 避免脏读,但是易出现【不可重复读】,两次执行同样的查询,可能得到不一样的结果。大多数数据库系统的默认隔离级别都是Read committed(mysql不是)。
Repeatable read(重复读) 在开始读取数据(事物开启时),不再允许修改操作,避免不可重复度,可能出现【幻读】。该隔离级别是Mysql默认的隔离级别。
Serializable(序列化)事物串行化执行(在读取的每一行数据上都加锁)可以避免脏读、不可重复读与幻读,但是这种事物隔离级别效率较低,比较耗费数据库性能,一般不使用
【脏读】:一个事务读取到另外一个事务未提交的数据

事例:老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。

分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读

【不可重复读】一个事务读取到另外一个事务已经提交的数据,也就是说一个事务可以看到其他事务所做的修改

事例:程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的…

分析:这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读

【幻读】是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。

事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。

大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。Mysql的默认隔离级别是Repeatable read。

Oracle数据库不支持READ UNCOMMITTED和REPEATABLE READ隔离级别

5、事务的实现机制(https://juejin.cn/post/6991733387505434655)

事务的实现相对比较复杂,它是基于多种机制和方案共同保障而做到了。
比如,通过回滚机制实现事务的原子性;借助锁机制和MVCC(多版本并发控制)保证事务的隔离性和并发性;基于WAL(预写日志系统Write-Ahead Logging)、Checkpoint、Crash Recovery、重做或回滚等机制实现事务的持久性;通过约束一致性(唯一索引、外键、check、NOT NULL等完整性约束)和数据一致性(由原子性、隔离性、持久性等机制保证,尤其是持久性保证数据的一致不丢失),实现数据库系统的稳定状态。

回滚机制
回滚机制,就是在事务发生错误的情况下,数据库可以恢复到事务开始时的状态的能力。它是保证原子性的核心,也是事务并发调度的重要属性。

锁Locking
锁(加锁或封锁)机制,是实现事务的并发控制与隔离性的最常用的方案。

锁机制就是事务在对某个数据对象(如表、记录等)操作之前,先向系统发出请求,对其加锁;加锁后事务就对该数据对象有了一定的控制,在释放它的锁之前,其它的事务不能更新此数据对象。

加锁后具有什么样的控制由锁的类型决定,不同类型的锁综合作用,保证多个事务并发操作数据时,事物间各自的隔离和互不干扰。

MVCC
多版本并发控制(Multi-Version Concurrency Control,简称“MVCC”)是一种并发控制的方法,用于实现数据库的并发访问。与封锁机制的目标类似,但是比加锁有着更高的并发度、可以减少事务阻塞等。任何情况下MVCC都不会阻塞读操作,只有在多个写操作更新相同行记录时,才会有阻塞事务的冲突。

MVCC协议确保每个事务都只看到与该事务启动时的快照视图相一致的版本数据;每个事务只看到数据的一个快照,该快照只包含那些在事务启动时已经提交的数据。这个快照并不等于数据的当前状态。

MVCC的关键是要高效地维护每一行记录的不同版本,每个版本表示事务开始时,所有已经提交的数据的最新结果。

MVCC实现的也是事务的隔离和并发控制。它的目的是,让读操作不阻塞写操作,写操作不阻塞读操作,提高数据库访问的并发度。数据读取的是行记录的最新版本。写操作会创建独有的隔离的行记录副本,用以更新。

WAL
WAL(Write-Ahead Logging)是一种实现事务日志的标准方法。WAL的核心机制是,在对数据进行任何变更之前,先确保对数据库的所有修改操作都已经记录到事务日志中,从而保证事务的更新操作不会丢失。即先写日志到磁盘的机制,数据可以后续再刷新到磁盘。

WAL的目的在于保证日志的完整和正确,从而完成事务的持久性,支持数据库的灾难恢复(Crash Recovery),防止数据丢失,而且先写日志将数据的随机写改为日志的顺序写,这样也提高了数据库的处理性能

Checkpoint
Checkpoint是事务日志中的检查点。

Checkpoint机制的基本过程是这样的。当产生检查点时,到该检查点为止的所有的内存数据页都会被刷新写入到硬盘中,并在WAL事务日志中写入该检查点记录。也就是,检查点机制保证检查点之前的所有数据都已经写入到了数据库文件中。

数据库如果发生故障,恢复过程中会从最后的检查点开始,重做这个检查点之后的事务日志,即重做日志,使数据库恢复到一致性状态。

从异常恢复的角度来说,检查点之前的WAL日志是不需要的,这样就可以循环使用日志文件;不同的数据库有不同的处理机制,比如MySQL的binlog则不会循环覆盖,而是归档便于数据库的同步或重现恢复(当然binlog中没有检查点的概念,而是redolog中使用checkpoint)。

Checkpoint机制的作用在于:保证数据库的一致性,将缓冲区的脏数据写入硬盘,使内存和硬盘中的数据一致;使WAL日志尽快失效,便于重用和节省占用的硬盘空间;缩短数据库崩溃的恢复时间,如果不进行Checkpoint,WAL日志就一直不会失效,数据库系统在重启过程中需要重做的WAL事务日志就会非常庞大,导致恢复时间过长

Crash Recovery
Crash Recovery也叫crash-safe。crash-safe的能力就是做到,即使数据库异常重启,已经提交的记录都不会丢失。

数据库系统发生异常终止的原因有很多,通常都是由于数据库服务进程被强行终止。可能的情况有:内存不足时被OOM Killer终止(Out Of Memory Killer)、操作系统崩溃、服务器停机、服务器重启、服务器发生硬件故障等。

Crash Recovery机制保证当发生数据库系统(服务进行)异常终止时,数据库能够从灾难中恢复到某个一致性状态。故障恢复/灾难恢复/异常恢复,就是要做到,只要硬盘上的数据和日志没有丢失,数据库就不会丢失数据,这是数据库系统的设计目标。

不丢失数据的含义如下:

数据库实例或服务还能再次启动。
数据库重启后,已经提交的事务还在。
当数据库处于一致性状态时,不会出现数据错乱的情况。
Crash Recovery通常需要多个文件和操作共同配合才能实现。比如MySQL中可能会用到redolog、undolog、binlog、数据文件等;PostgreSQL中可能会用到WAL日志文件、控制文件(Checkpoint等信息)、事务状态文件、数据文件等。

数据库锁https://juejin.cn/post/6844904179979255816
为什么需要锁?

在并发环境下,如果多个客户端访问同一条数据,此时就会产生数据不一致的问题,如何解决,通过加锁的机制,常见的有两种锁,乐观锁和悲观锁,可以在一定程度上解决并发访问。

备注:锁的行为和顺序是和存储引擎相关的,以同样的顺序执行语句,有些存储引擎会产生死锁,有些则不会

锁分类
按操作划分:DML锁,DDL锁
按锁的粒度划分:表级锁、行级锁、页级锁
按锁级别划分:共享锁、排他锁
按加锁方式划分:自动锁、显示锁
按使用方式划分:乐观锁、悲观锁

乐观锁

乐观锁总是假设最好的情况,每次去拿select数据的时候都认为别人不会修改,所以不会上锁,但是在更新update的时候会判断一下在此期间别人有没有去更新这个数据。乐观锁适用于多读的应用类型,因为乐观锁在读取数据的时候不会去加锁,这样可以省去了锁的开销,加大了系统的整个吞吐量。

常用实现方式

1、版本号机制

      版本号机制就是在表中增加一个字段,version,在修改记录的时候,先查询出记录,再每次修改的时候给这个字段值加1,判断条件就是你刚才查询出来的值。

      示例

新增如下用户信息表
CREATE TABLE user_info (
id bigint(20) NOT NULL AUTO_INCREMENT COMMENT ‘主键ID’, user_name varchar(64) DEFAULT NULL COMMENT ‘用户姓名’, money decimal(15,0) DEFAULT ‘0’ COMMENT ‘剩余金额(分)’, version bigint(20) DEFAULT ‘1’ COMMENT ‘版本号’, PRIMARY KEY (id) USING BTREE ) ENGINE=InnoDB COMMENT=‘用户信息表’;
新增一条数据
INSERT INTO user_info (user_name, money, version) VALUES (‘张三’, 1000, 1);

操作步骤
步骤 线程A 线程B
1 查询张三数据,获得版本号为1(SELECT * FROM user_info WHERE user_name = ‘张三’😉
2 查询张三数据,获得版本号为1(SELECT * FROM user_info WHERE user_name = ‘张三’😉
3 修改张三金额,增加100,版本号+1(UPDATE user_info SET money = money + 100, version = version + 1 WHERE user_name = ‘张三’ AND version = 1;),返回修改条数为1
4 修改张三金额,增加200,版本号+1(UPDATE user_info SET money = money + 200, version = version + 1 WHERE user_name = ‘张三’ AND version = 1;),返回修改条数为0
5 判断修改条数为是否为0,是返回失败,否则返回成功
6 判断修改条数为是否为0,是返回失败,否则返回成功
7 返回成功
8 返回失败
2、 CAS算法

CAS即 compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS 算法涉及到三个操作数:

需要读写的内存值 V(主内存中的变量值)
进行比较的值 A(克隆下来线程本地内存中的变量值)
拟写入的新值 B(要更新的新值)
当且仅当 V 的值等于 A 时,CAS 通过原子方式用新值 B 来更新 V 的值,否则不会执行任何操作(比较和替换是一个 native 原子操作)。一般情况下,这是一个自旋操作,即不断的重试,以給上表张三的金额增加100(张三的金额初始值为1000)举例说明CAS算法流程

步骤 线程A 线程B
1 从表中查询出张三的money=1000,设置进行比较的值为1000,要写入的新值为money + 100 = 1100(V:1000–A:1000–B:1100)
2 从表中查询出张三的money=1000,设置进行比较的值为1000,要写入的新值为money + 100 = 1100(V:1000–A:1000–B:1100)
3 更新张三的金额(UPDATE user_info SET money = money + 100 WHERE user_name = ‘张三’ AND money = 1000;),返回更新条数为1
4 更新张三的金额(UPDATE user_info SET money = money + 100 WHERE user_name = ‘张三’ AND money = 1000;),返回更新条数为0
5 跳出循环,返回更新成功
6 自旋再次从表中查询出张三的money=1100,设置进行比较的值为1100,要写入的新值为money + 100 = 1200(V:1100–A:1100–B:1200)
7 更新张三的金额(UPDATE user_info SET money = money + 100 WHERE user_name = ‘张三’ AND money = 1100;),返回更新条数为1
8 跳出循环,返回更新成功

悲观锁

悲观锁每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞住,直到我释放了锁,别人才能拿到锁,这样的话,数据只有本身一个线程在修改,就确保了数据的准确性。因此,悲观锁适用于多写的应用类型。

适用场景

悲观锁比较适合写入操作比较频繁的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
乐观锁比较适合读取操作比较频繁的场景,如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,降低了系统的吞吐量。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值