数据库

Posted by Liber Sun on July 10, 2018

序列化和反序列化

序列化:将一个对象转成一串二进制表示的字节数组,通过保存或者转移这些字节数据来达到持久化的目的。 反序列化:将字节数组重新构建为对象。

那我们做序列化和反序列化是为了什么呢? 为了 持久化。持久化,就是我们将内存中的对象给 持久 的存储起来。这里我们就有个疑问?我们不用使用序列化 也可以将对象存储到文件中,那么为什么需要序列化呢?

  • 存储到数据库\文件中
  • 网络传输
  • RMI(remote method invocation, 它是一种机制,能够让在某个Java虚拟机上的对象调用另一个Java虚拟机中的对象上)传输对象
  • 读取JavaBean状态

持久化

我们经常有持久层这个说法,那么持久层是什么呢?我们先来谈谈 数据的 持久性 ,持久性是数据的一个属性,其确保即使实在应用的生命周期以外数据仍是可用的。 对于Java而言,持久性确保了即使是在创建对象的应用停止执行之后对象的状态仍是可访问的。

而我们如何保证持久性呢,有两种手段,一个是将数据保存在文件中,一个是将数据保存到数据库中。

那么持久层的概念就油然产生了,分离了业务逻辑和数据库代码的层面既是持久层。持久层封装了存储和检索数据库中的数据的方式。

E-R模型

实体-关系 数据模型,

实体:描述事物的抽象对象,就是实体 关系:实体和实体之间的关系

 - 一对一
 - 一对多
 - 多对多

数据库

数据库是保存有组织的数据的容器。(数据库通常体现为一个文件或者一组文件)

*数据库管理系统(DBMS)是用来访问、操作数据库的软件,我们常说的MySQL,Oracle就是DBMS。

SQl,结构化查询语言。是一种专门用来与数据库通信的语言。

NoSQL

Mongodb

MongoDB提出是文档(Document)、集合(Collection)的概念,用BSON(Binary JSON)作为其数据模型结构,其结构是面向对象的,因此我们可以对其进行嵌套。 比如班级当中直接包含学生,而不用单独的构建 班级 和 学生两个表。

Mongodb是非SQL数据库,并没有固定的Schema,可以让数据的存储结构更灵活,存储速度更加快,并且具有指向不确定类型值的建。 但是它无法做联结操作,不支持事务,但是支持多种原子更新操作。

关于MongoDB Schema的设计

  • 内嵌和引用:当子对象总是出现在父对象的上下文中,使用内嵌,否则将子对象单独列为集合。
  • 一对多的关系:在”多”的集合中存储”一”的id指向”一”。
  • :存在树的情况,请在结点中包含path字段,指定节点祖先的id
  • 关于事务 :如果需要事务支持,那么只能选择另一种数据库,或者提供补偿性事务来解决事务的问题。

在关于schema 的设计中要注意一些原则,比如:

  • 不能创建没用的索引
  • 不能在同一个字段中存不同的类型
  • 不能把多类实体都放在一个集合里 不能创建体积大、嵌套深的文档
  • 不能过多的创建集合,集合、索引、数据库的命名空间都是有限的
  • 不能创建无法分片的集合

关系的设计

  1. One-to-Few

列子:人和地址,我们可以将地址以数组的形式内嵌在人的文档中。(人的地址与人息息相关的,且不易增长变化) 优点:我们无需单独的查询来读取内嵌文档的细节。 缺点:无法单独作为实体来访问内嵌文档的细节

  1. One-to-Many

列子:顾客和订单,随着事件的增长,订单数会越来越多。 解决方案,将订单的ID存储在一个数组中,然后内嵌到顾客的文档中。 在这种设计之下,顾客和订单是两个文档。 优点:单独的子文档,非常方便于搜索与更新,但是这种子文档的查询往往需要两个查询。 同时也便于扩展到N-TO-N的模式。

  1. One-to-Squillions

这是一对超多的关系,这个超多的概念可以理解为千万级别的数据量。这样的话及时我们在父文档中存储数组,也存在超过16MB的情况,此时我们可以采用父引用,及在子文档中存储父亲的引用。

总结,在设计Mongodb的数据库时,我们需要综合如下进行考虑:

对于子类文档,是否需要单独存储? 文档之间的关系是什么?One-to-Few,One-to-Many或者One-to-Squillions。

  • 如果是one-to-few关系,并且无须从父文档之外的方式访问N类文档,内嵌N类文档;
  • 如果是one-to-many关系,并且希望N类文档以独立的文档存储,那么使用N类文档的引用数组,并内嵌在父文档中;
  • 如果是one-to-sequillions关系,在N类文档中存储一个指向one类文档的引用。

Redis

SQL

基础

数据库的基础结构可以分为:

  • :某种特定类型数据的结构化清单。
  • :表中的一个字段,所有的表都是由一个或者多个列构成的。每个列都具备相应的数据类型。
  • :每一条数据都是按行进行存储的。行也经常被称为记录
  • 主键:表中的每一行都有可以唯一标识自己(不同行的主键不能重复)的一列或者列的组合,这样的列被称为主键
  • 外键:外键为列中的一种,它包含了另一个表的主键值,定义了两个表之间的关系

关系数据库的关系是什么呢?将信息拆分为多个表,通过表与表之间的联系,将信息组合在一起。

数据类型

以下是典型的数据类型:

数据类型 说明
CHAR 1~255个字符的定长串
TEXT 最长长度为64K的文本
VARCHAR 长度可变,最多不超过255个字节
BIT 1~64位
INT -2^42到2^42-1
DATE YYYY-MM-DD
BLOB 64KB的二进制数据

常用语法

创建表:

CREATE TABLE customers
(
    cust_id        int      NOT NULL AUTO_INCREMENT,
    cust_name      char(50) NULL UNIQUE,
    cust_address   char(10) NULL,
    cust_city      char(10) NULL,
    cust_score     int      DEFAULT 100,
    company_id     int      NOT NULL
    PRIMARY KEY (cust_id)
    CONSTRAINT fk_id foreign key (company_id) references company (company_id) //指向公司表
)ENGINE=InnoDB

默认引擎通常是MyISAM,MyISAM支持全文搜索。 InnoDB是可靠的事务处理引擎。 MEMORY是存储在内存中,因此速度快,特别适合临时数据。 注意外键联结的两个表,应该是同一个引擎。

创建视图:

CREATE VIEW viewName AS 
SELECT vend_name,prod_name,prod_price FROM vendors,products WHERE vendors.vend_id=products.vend_id;

视图,是一个虚拟的表。往往是对一个复杂的SQL的视图封装,有利于简化SQL、重用SQl、保护数据。

创建索引:

CREATE INDEX indexName 
ON customers (cust_name ASC,cust_city DESC);

更新表:

ALTER TABLE customers
(
    ADD  cust_add CHAR(20),
    DROP cust_drop
);

ALTER TABLE customers(
ADD CONSTRAINT fk_id 
FOREIGN KEY (company_id) 
REFERENCES company (company_id)
)

删除表:

DROP TABLE customers;//删除

删除表中的行:

DELETE FROM customers
Where cust_id = 1004;

DELETE FROM customers;//清空表,效率较低
TRUNCATE TABLE customers;//清空表

重命名表:

RENAME TABLE customers TO customers_rename;

插入行:

INSERT LOW_PRIORITY INTO customers
(
cust_name,
cust_address,
cust_city
)
VALUES
('Sunlingzhi',
'ShuangLiu',
'ChengDu'),
('Tanghanwen',
'ZiYang',
'ChengDu');

LOW_PRIORITY: 能使插入语句的优先级降低。

更新表中的一行或多行:

UPDATE IGNORE customers
SET cust_name = 'Tanghanwen_2',
    cust_address= 'Ziyang_2'
Where cust_id = 1004;

IGNORE: 如果有多条更新,当不使用Ignore时,一条更新出错,则全部都会恢复原状,当使用Ignore时,会强制刷新。

SELECT column_name(s) FROM table1
UNION
SELECT column_name(s) FROM table2;

将两个结果进行联合,UNION 内部的每个 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每个 SELECT 语句中的列的顺序必须相同。 UNION默认采取不同的值,利用UNION ALL 允许重复的值。

查询

初级
  1. 选择-select

SELECT 列名 FROM 表名 ; 返回某表全部数据的特定一列 SELECT 列名1,列名2,列名3 FROM 表名 ; 返回某表全部数据的特定几列 SELECT name, CONCAT(url, ', ', alexa, ', ', country) AS site_info FROM Websites; 返回name、site_Info SELECT * FROM 表名 ; 返回某表全部数据的所有几列,通常会降低检索和应用程序的性能,当然它最大的作用是检索中名字未知的列 SELECT DISTINCT 列名 FROM 表名 ; 返回某表全部数据的特定一列,且不重复 SELECT 列名 FROM 表名 LIMIT 5; 这里的5是个数,即返回前5行,某表的特定一列 SELECT 列名 FROM 表名 LIMIT 5,5; 从第5行开始,返回5行,某表的特定一列

注意点: Distinct 是应用于所有列,而不是前置列,如果多行的某一列重复,而其他列不重复,这个时候全部都会显示。 limit 1 是返回第一行的记录。 limit 0,1 也是返回第一行记录; limit 1,1 返回的是第二行记录。

  1. 排序-order by

SELECT 列名 FROM 表名 Order by 列名; 按照特定列的升序大小,按顺序返回某表全部数据的特定一列SELECT 列名 FROM 表名 Order by 列名1,列名2; 注意这里的排序是完全按照规定的顺序进行的,即只有在列名1排序完,然后再对列名1相同的再进行排序。 SELECT 列名 FROM 表名 Order by 列名 DESC; 按照特定列的降序大小,按顺序返回某表全部数据的特定一列

注意点: 在字典的排序中, A和a 往往被视为相同的,这是大多DBMS的默认行为,如果要区分,必须请求数据库管理员的帮助。 DESC 是应用于后置列,意味着每个列都需要单独的设置DESC。

Select prod_price from products order by prod_price desc limit 1;

  1. 过滤-where

Select 列名 from 表名 where condition :返回满足条件的数据的特定列

conditions的操作符表格如下:

操作符 说明
= 等于
<> != 不等于
< <= >= > 小于 小于等于 大于等于 大于
between a and b 返回指定的值之间,连续的区间(不同的数据库可能边界取值不一致)
in (a, b, c, d) 具备a、b、c、d其中一种即返回
is null 返回具备空值的列

Select prod_price from products where prod_price between 5 and 10 order by prod_price desc limit 1;

  1. 过滤组合

我们可以利用and,or,对where语句进行组合。同时and的计算优先级大于or,所以为了消除歧义,推荐使用()。

同样对于过滤条件我们可以使用 not 进行否定

Select prod_price from products where prod_id not in (1, 2, 3);

  1. 通配符进行过滤-like

前面的过滤,往往存在已知条件。但是这种过滤方法并不是在任何时刻都是好用的。列如:如何搜索名字中包含文本“Nike”的所有产品,这就必须考虑通配符的使用。

%:多字符 SELECT prod_id,prod_name FROM products where prod_name like '%sun%' ; 返回name包含 sun SELECT prod_id,prod_name FROM products where prod_name like 's%n' ; 返回name包含 开头是s 结束是n SELECT prod_id,prod_name FROM products where prod_name like 's%1n' ; 返回name包含 开头是s 结束是n 中间1个字符

_: 单字符 SELECT prod_id,prod_name FROM products where prod_name like 's_n' ; 返回name包含 开头是s 结束是n 中间1个字符

通配符不能过度使用,效率极低。

  1. 正则表达式

SELECT prod_id,prod_name FROM products where prod_name regexp '1000' ; 返回name包含1000的

注意 SELECT prod_id,prod_name FROM products where prod_name regexp '1000' ; 返回name包含1000的 SELECT prod_id,prod_name FROM products where prod_name like '1000' ; 是匹配全部,如果数据中包含1000是无法通过like检测出来的。

  1. 字段二次加工

数据库存储的数据一般对于应用程序而言都是无法直接使用的,所以引入计算字段、拼接字符串、引入别名,可以减轻应用程序对数据进行处理的操作。我们通过计算字段,可以对存储在表中的数据进行转换、字段屏蔽以及格式化,以此来满足应用程序的需求。(好的后端的表现,哈哈哈哈!!!当然这份工作可以交由应用程序去完成,但是DBMS设计之初就是为了这样的处理)。

计算字段: SELECT a*b FROM products; 返回ab; 别名: SELECT a*b as a_b FROM products; 返回a_b, 其值为ab; 拼接: SELECT Contact(a,'_',b) as a_b FROM products; 返回a_b, 其值为a_b;

注意AS还可以作用到表名上,以减少SQL的长度。

  1. 数据处理函数

相对来说SQL语言虽然是存在差异的,多数的SQL语言是可进行移植的。 但是数据处理函数(代码),在不同的DBMS中往往都有不同的差异。 因此对于使用到的数据处理函数,必须保证他人能够理解,不然不要使用。

大多数SQL实现并支持了以下的函数:

  • 处理文本的
  • 处理数值的
  • 日期和时间
  • DBMS的系统信息函数。

这里特别对特别函数进行说明

SoundexSELECT product_name FROM products where Soundex(product_name) = Soundex('Y lie'); 找出读音与Y lie 相似的数据。

  1. 我们有时候为了应付年终汇报的时候,需要做一些报表,而不是实际的检索再对数据进行处理,我们希望利用SQL直接进行数据的汇总。

AVG COUNT MAX MIN SUM 是5个基础的聚集函数。

SELECT count(*) FROM products where product_location=1001; 返回地址为1001的数量

  1. 分组

针对不同的分组做事情,避免了整个表的计算。

SELECT count(*) FROM products group by product_location; 将数据按product_location分组后,返回数量

当然我们还可以像where一样 对分组进行过滤, 但是where是应用于行的,实在分组之前;针对分组之后,我们采用having实现过滤

SELECT cust_id count(*) as orders FROM orders group by cust_id having Count(*) >= 2; 返回按客户id分组后的订单量大于2的数据

注意我们在使用group by时 应该给出 order by, 保证数据的正确排序,不能过分依赖于group by 的默认排序(默认以group by的字段升序排序)。


在了解了这么多关键字之后,我们要确定一下关键字的顺序。

select as from where union group by having order by limit

高级

子查询,就是Select查询的嵌套,子查询由内向外依此处理。

联结表,我们为什么需要联结?因为数据在关系数据库中往往被分解为多个表,以更高效的存储。如何利用单条Select,将多个表的,乃至多个数据库中的数据检索出来,这就提出了联结表的使用。

SELECT vend_name,prod_name,prod_price FROM vendors,products WHERE vendors.vend_id=products.vend_id; 当不设定where子句时,返回的结果为笛卡儿积(交叉联结),即第一个表的行数*第二个表的行数。因此,我们在使用联结的时候往往都有where子句对数据进行过滤

SELECT vend_name,prod_name,prod_price FROM vendors INNER JOIN products ON vendors.vend_id=products.vend_id; 与上面的where联结是一样的,区别在于使用了 INNER JOIN ON 关键字,特别辨识了这是内部联结

当列在两个表中都是相同的时候,可以使用自然联结SELECT vend_name,prod_name,prod_price FROM vendors NATURE JOIN products; 内部联结将一个表中的行与另一个表中的行相关联,但有时候需要包含没有关联的行的那些行,这个时候就需要外部联结

SELECT customers.custs_id,orders.order_nunm
FROM customers INNER JOIN orders ON customers.cust_id=orders.cust_id;

检索用户和用户的订单信息,不能包括没有订单的用户

SELECT customers.custs_id,orders.order_nunm
FROM customers LEFT OUTER JOIN orders ON customers.cust_id=orders.cust_id;

检索用户和用户的订单信息,包括那些没有订单的用户。

cust_id order_num 101 2000 102 null //显示左表的所有行 103 3000

SELECT customers.custs_id,orders.order_nunm
FROM customers RIGHT OUTER JOIN orders ON customers.cust_id=orders.cust_id;

cust_id order_num 101 2000 null 2500 //显示右表的所有行 103 3000

Mysql

MYSQL提出的是带行和列的数据表(Table)的概念,

SQL类型的数据库是正规化的,可以通过主键或者外键的约束保证数据的完整性与唯一性,所以SQL类型的数据库常用于对数据完整性较高的系统

mysql是关系数据库,本质来说就是一个软件。在我们复杂的软件世界,本质就两种产品,一种是CPU密集,另一种就是I/O密集。而数据库自然是一个I/O密集的软件。

特点内容:

存储过程 将多条SQL语句的集合存储为一个存储过程,可以将之视之为批处理文件。 游标 可以在Select出来的数据集中,进行前进、后退。 触发器 定义事件,当事件发生时,某条SQL语句自动执行。 事务处理 事务用于维护数据库的完整性,多条SQL要么全部执行,要么都不执行。 全球化和本地化 安全管理 数据库维护 改善性能

mysql架构图

Server层

  • 连接池,当client连接server时,往往会存在连接池的情况。
  • service & utilitis,mysql的主备信息用具, 分区工具等等。
  • Caches,如果是查询语句,在cache中命中结果则返回;相反的如果是更新语句在执行更新的时候, cache要被更新。
  • SQL parser,检验SQL语句。
  • Optimizer,经过SQL parser。mysql就理解你的意图了, 但是执行的时候如果有多个索引,我们先查那个呢? 这就是你在网上搜索到怎么建mysql索引的文章大堆大堆的原因,因为选择不同的索引,显然效率是不一样的。而如何看mysql是如何执行的呢? 可以使用explain查看。那面试官问你为何一个查询慢, 很显然你应该用explain命令 mysql> explain select * from Student; 查看执行的情况。

存储引擎层

存储引擎说白了就是如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术的实现方法。 mysql 的默认存储引擎是InnoDB, 重点掌握两种即可。而谈及存储引擎, 我们最重要的是索引, 因为它直接决定了数据如何存取。

在mysql数据库中,MyISAM引擎和InnoDB引擎的主键和索引都使用了B+树,但是二者对B+树的使用略有区别,

MyISAM

在MyISAM引擎中,B+树的叶子节点的数据部分保存的是数据库记录的地址,主键索引和辅助索引都是如此,这种索引方式叫做“非聚集索引” 索引文件和数据文件是分离的。

InnoDB

在InnoDB引擎中,主键索引和辅助索引是不同的。

InnoDB引擎的 主键索引 在叶子节点中的数据部分,存储的是数据库记录本身,这种索引方式叫做“聚集索引” InnoDB的数据文件本身就是索引文件,表数据文件本身就是按B+Tree组织的一个索引结构,这棵树的叶节点data域保存了完整的数据记录。 叶节点包含了完整的数据记录。这种索引叫做聚集索引。因为InnoDB的数据文件本身要按主键聚集,所以InnoDB要求表必须有主键(MyISAM可以没有),如果没有显式指定,则MySQL系统会自动选择一个可以唯一标识数据记录的列作为主键,如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。

InnoDB引擎的 辅助索引 , 叶子节点中的数据部分保存的是主键索引的关键字,也就是说,在查询时,用辅助索引查到了数据之后,还要到主键索引中再查一次,才能得到真正的数据,

GraphDB

图数据库

事务

事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)

一个数据库事务通常包含对数据库进行读或写的一个操作序列。。它的存在包含有以下两个目的:

    1. 为数据库操作提供了一个从失败中回复到正常状态的方法,同时提供了数据库即使在异常状态下仍保持一致性的方法。
    1. 当多个应用程序并发访问数据库时,能够在这些应用程序中提供一个隔离的方法,以防止彼此操作互相干扰。

当一个事务被提交给了DBMS(数据库管理系统),则DBMS需要确保该事务中的所有操作都成功完成且其结果被永久保存在数据库中,如果事务中有的操作没有成功完成,则事务中的所有操作都需要被回滚,回到事务执行前的状态(要么全执行,要么全都不执行); 同时,该事务对数据库或者其他事务的执行无影响,所有的事务都好像在独立的运行。

ACID特性

并非所有的对数据库的操作都被称作数据库事务。事务应当满足4个属性:原子性、一致性、隔离性以及持久性,这四个属性通常称之为ACID特性。

原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。 持久性(Durability):一个事务一旦提交,他对数据库的修改应该永久保存在数据库中。

事务隔离

隔离性表示一个事务的修改结果在什么时间能够被其他事务看到,隔离性定义了不同的隔离级别,

  • Read uncommitted
  • Read committed
  • Repeatable reads
  • Serializable
隔离级别           脏读   幻读   不可重复读
未提交读(RUC)       是     是      是
已提交读(RC)        否     是      是
可重复读(RR)        否     是      否
可串行化            否     否      否

脏读:一个事务读到了另一个事务未提交的数据。 幻读:一个事务读到了另一个事务insert的数据 不可重复读:多次读取统一数据返回的结果不一致。同脏读不同,这是读取已经提交的数据;同幻读也不同,这边是更新数据,幻读是插入数据。

请参考

MVCC(Multi Version Concurrency Control)

隔离级别依次增强,但是导致的问题是并发能力的减弱。各种数据库厂商会对各个隔离级别进行实现。和Java中的多线程问题相同,数据库通常使用锁来实现隔离性。

最原生的锁,锁住一个资源后会禁止其他任何线程访问同一个资源。但是很多应用的一个特点都是读多写少的场景,很多数据的读取次数远大于修改的次数,而读取数据间互相排斥显得不是很必要。所以就使用了一种读写锁的方法,读锁和读锁之间不互斥,而写锁和写锁、读锁都互斥。这样就很大提升了系统的并发能力。之后人们发现并发读还是不够,又提出了能不能让读写之间也不冲突的方法,就是读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了,不同的事务session会看到自己特定版本的数据。这就是MVCC(Multi Version Concurrency Control)

数据库的🔒

数据库可以通过锁机制来实现事务的隔离。

锁的粒度

行锁

行锁,由字面意思理解,就是给某一行加上锁,也就是一条记录加上锁。

当出现update、delete和insert时,会进行 行锁 .

对于select可进行显示加锁

SELECT * from city where id = 1  lock in share mode; 

SELECT * from city where id = 1  for update;  行锁的锁定颗粒度是最细的,只针对操作的当前行进行加锁。并发情况下,产生锁等待的概率较低,支持较大的并发数,但开销大,加锁慢,而且会出现死锁。(InnoDB使用前提是检索数据存在索引,在无索引的情况下InnoDB会使用表锁,在高并发的情况下,冲突严重)

死锁出现的原因在于:锁是逐步获得的,存在争抢。

表锁

tabLock是利用共享锁将表进行🔒 tabLockX是利用排他锁将表进行🔒

当执行select之间,会自动对涉及的所有表加 读锁 lock table city read; 当前线程可以读,其他的也可以读,但是写操作被阻塞

当执行更新操作时,会自动给涉及的表加 写锁 lock table city write; 当前线程可以做任何事情,但是别的线程所有操作都被阻塞

表锁的锁定颗粒度在MySQL中是最粗的,InnoDB、MyISAM引擎中都有应用,对当前整张表加锁。不适合高并发的场景,但开销小,加锁快,不会出现死锁,发生锁冲突的概率最大。

页锁

页锁的粒度介于行锁和表锁之间,应用于BDB引擎,并发度一般,开销和加锁速度也介于行锁和表锁之间。

加锁的机制

乐观锁

乐观锁不是数据库自带的,需要我们自己去实现。乐观锁是指操作数据库时(更新操作),想法很乐观,认为这次的操作不会导致冲突,在操作数据时,并不进行任何其他的特殊处理(也就是不加锁),而在进行更新后,再去判断是否有冲突了。

通常实现是这样的:在表中的数据进行操作时(更新),先给数据表加一个版本(version)字段,每操作一次,将那条记录的版本号加1。也就是先查询出那条记录,获取出version字段, 如果要对那条记录进行操作(更新), 则先判断此刻version的值是否与刚刚查询出来时的version的值相等,如果相等,则说明这段期间,没有其他程序对其进行操作,则可以执行更新,将version字段的值加1;如果更新时发现此刻的version值与刚刚获取出来的version的值不相等,则说明这段期间已经有其他程序对其进行操作了,则不进行更新操作。

乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。但如果直接简单这么做,还是有可能会遇到不可预期的结果,例如两个事务都读取了数据库的某一行,经过修改以后写回数据库,这时就遇到了问题。

悲观锁

悲观锁就是在操作数据时,认为此操作会出现数据冲突,所以在进行每次操作时都要通过获取锁才能进行对相同数据的操作,这点跟java中的synchronized很相似,所以悲观锁需要耗费较多的时间。另外与乐观锁相对应的,悲观锁是由数据库自己实现了的,要用的时候,我们直接调用数据库的相关语句就可以了。

悲观并发控制主要用于数据争用激烈的环境,以及发生并发冲突时使用锁保护数据的成本要低于回滚事务的成本的环境中。

在数据库中,悲观锁的流程如下:

在对任意记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。 具体响应方式由开发者根据实际需要决定。 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。 其间如果有其他对该记录做修改或加排他锁的操作,都会等待我们解锁或直接抛出异常。

悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会;另外,在只读型事务处理中由于不会产生冲突,也没必要使用锁,这样做只能增加系统负载;还有会降低了并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数

说到这里,由悲观锁涉及到的另外两个锁概念就出来了,它们就是共享锁与排它锁。共享锁和排它锁是悲观锁的不同的实现,它俩都属于悲观锁的范畴。

兼容性

共享锁

共享锁指的就是对于多个不同的事务,对同一个资源共享同一个锁。 读锁就是一种共享锁

排他锁

排它锁与共享锁相对应,就是指对于多个不同的事务,对同一个资源只能有一把锁。 写锁是一种排他锁

锁模式

记录锁

间隙锁

next-key锁

意向锁

插入意向锁

范式与反范式

范式

将数据分散到多个不同的集合,不同的集合之间相互引用数据。 存储一份,多处引用,可以尽可能减少数据的冗余。 数据表的的更新更快。 查询需要多表关联,导致性能降低。

范式规则

第一范式:字段是最小单元不可再分 第二范式:表中的字段必须完全依赖全部的主键而非部分逐渐 第三范式:非主键外的所有字段必须互不依赖

反范式

将每个文档所需的数据都潜入在文档内部。 存储多份(冗余),可以一次取出需要的数据; 但是修改时需要多处进行修改,数据同步复杂。

索引

索引的本质是将无序的数据变成相对有序的数据

索引是对数据库表中一个或多个列的值进行排序(无序变为有序)的结构。索引具有以下的优点:

  • 通过建立唯一性索引,可以保证数据库表中的每一行数据的唯一性。
  • 建立索引,可以大大加快数据的检索速度(大大减少检索的数据量)
  • 帮助服务器避免排序
  • 将随机IO变为顺序IO
  • 可以加速表与表之间的链接。

虽然索引具有一系列的优点,但是我们并不能对表中的每一列创建一个索引。

  • 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
  • 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大。
  • 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。

数据库操作的演化

jdbc

JDBC(JAVA DataBase Connectivity), 通过JDBC我们可以在java程序和数据库之间建立链接,并通过java程序来操作数据库中的数据。

JDBC将应用程序开发者与底层的数据库驱动程序进行了解耦,是一个承上启下的中间层。

JDBC(是一系列的接口)不能直接访问数据库,必须依赖数据库厂商提供的JDBC驱动程序。JDBC为用户完成了以下三步工作:

  1. 同数据库建立链接,获取Statement。
  2. 向数据库发送SQL语句,JDBC API使用SQL语句来完成创建(create)、读取(read)、更新(update)和删除(delete)(CRUD)操作,由Statement执行
  3. 处理从数据库返回的数据ResultSet

这类代码还在很大程度上依赖于SQL,而SQL并非是跨数据库的标准,这使得从一种数据库移植到另一种数据库变得困难起来。 同时程序员需要处理太多的细节,一个简单的查询,就需要一大推代码,打开Connection、创建Statement,执行SQL,遍历ResultSet,获取结果,最后还必须记得关闭Connection。 另外我们通过JDBC执行SQL语言操作数据库这与JAVA面向对象的概念又是相违背的。

Spring的JDBCTemplate

JDBC已经能够满足大部分用户最基本的对数据库的需求,但是在使用JDBC时,应用必须自己来管理数据库资源。Spring对数据库操作需求提供了很好的支持,并在原始JDBC基础上,构建了一个抽象层,提供了许多使用JDBC的模板和驱动模块,为Spring应用操作关系数据库提供了更大的便利。

ORM

关系数据库是一张二维表格的集合体,利用JDBC的操作方式是面向表格的,不是面向对象的,使用JDBC的整个过程面向对象编程的思维观念很大程度上被遏制了 鉴于以上提出来的问题,在使用Java开发时,我们希望真正的建立一个对象型数据库,或者说至少使用起来看起来像一个对象型数据库,但是,目前常用的数据库又的确是关系型数据库,这一点短期内又无法改变。

ORM由此出现 ,ORM是JDBC的一层封装,提供了以面向对象的思维来操作数据库中的表的方式,ORM工具框架最大的核心就是封装了JDBC的交互,你不在需要处理结果集中的字段或者行或者列。借助于ORM可以快速进行开发,而无需关注JDBC交互细节。

ORM(对象关系映射),使得 对象数据库中的表 一一对应,构建了一个直接的映射关系。

ORM的出现是和面向对象编程的大潮息息相关的,它透明地把应用对象持久到关系数据库中的表的技术,它提供功能来执行完整的CRUD操作并鼓励面向对象的查询。

ORM框架

Hibernate

把java对象的属性用声明式的方式映射到数据库表中。 Hibernate提供了一个完整的ORM解决方案,但不提供对查询的控制权。 对于不太熟悉SQL的面向对象编程者来说,Hibernate是最佳选择。 Hibernate直接把Java对象映射到数据库表上

MyBatis

MyBatis不提供完整的ORM解决方案,也不提供任何的对象和关系模型的直接映射,他是一个半自动化的持久层框架。 不过,MyBatis给你提供了对查询的全面控制权, 他以SQL为中心,要求全面地控制SQL, 将java对象映射到SQL查询的结果上。

Spring的HibernateTemplate

针对ORM ,Spring 也提供了解决方案,可以轻松集成 Hibernate和ibatis,Spring的ORM模块并不是重新开发的,通过IOC容器和AOP模块对Hibernate的使用进行封装。

JPA

JPA(Java Persistence API)意即Java持久化API,是Sun官方在JDK5.0后提出的Java持久化规范(JSR 338,这些接口所在包为javax.persistence) JPA的出现主要是为了简化持久层开发以及整合ORM技术,结束Hibernate、TopLink、JDO等ORM框架各自为营的局面。JPA是在吸收现有ORM框架的基础上发展而来,易于使用,伸缩性强。 总的来说,JPA包括以下3方面的技术:

  • ORM映射元数据: 支持XML和注解两种元数据的形式,元数据描述对象和表之间的映射关系
  • API: 操作实体对象来执行CRUD操作
  • 查询语言: 通过面向对象而非面向数据库的查询语言(JPQL)查询数据,避免程序的SQL语句紧密耦合

JPA(Java persistence API),用于持久化的API,能够使应用程序以统一的方式访问持久层。

那么如何实现JPA呢?还是得依靠ORM思想。基于ORM思想实现的、满足JPA规范的框架就是JPA框架。 JPA框架为开发人员提供了一种对象/关系映射工具来管理Java应用中的关系数据。

SpringDataJPA

Spring Data JPA是Spring Data家族的一部分,可以轻松实现基于JPA的存储库。 此模块处理对基于JPA的数据访问层的增强支持。 它使构建使用数据访问技术的Spring驱动应用程序变得更加容易。其在JPA的基础上添加了另一层的抽象(Repository层),极大地简化了持久层开发以及ORM框架切换的成本。

Spring Data JPA和JPA的关系

spring推出的ORM框架,在jpa中是不存在dao层的, 它被 repository 替换了, repository 对dao层的代码进行了封装。

在利用Spring boot data JPA进行表设计的时候,表对象之间经常存在各种映射关系,如何正确将理解的映射关系转化为代码中的映射关系是关键之处。

1.@OneToOne 单向映射

@Entity
public class Record {
    @OneToOne
    @JoinColumn(name = "user_id",referencedColumnName="id")
    private User user;
}

2.@OneToMany

@Entity
public class Record {
    @OneToMany(cascade = CascadeType.REMOVE,mappedBy="record")
    @JsonManagedReference
    private List<Problem> problems;
}

@Entity
public class Problem {
    @ManyToOne()
    @JoinColumn (
        name = "record_id",
        referencedColumnName = "id")
    @JsonBackReference
    private List<Problem> problems;
}

record_id 时problem表中关联record的字段

3.@ManyToMany 在多对多的映射中会生成一张中间表,其表名和字段名均有默认的命名规则

@Entity

public class User extends BaseEntity {
    @Id
    @GeneratedValue
    private Integer id;
    
    @ManyToMany (fetch= FetchType.EAGER)
    @JsonManagedReference
    @JoinTable(name = "SysUserRole",
            joinColumns = { @JoinColumn(name = "id") },
            inverseJoinColumns ={@JoinColumn(name = "roleId") })
    private List<SysRole> roleList;
}

@Entity

public class SysRole {
    @Id@GeneratedValue
    private Integer id;

    @ManyToMany
    @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="id")})
    private List<User> users;
}

一对一与多对一,利用外键进行关联,若未设置级联删除,而删除被控方时会有外键约束。

多对多,用中间表关联,删除中间表,不会对双方造成影响。

OGM

上述的大多数描述都是SQL数据库,针对ORM的NOSQL实现有(对于非关系数据库来说ORM,应该被OGM替代更为准确)

  • MongoDB官方提供的Morphia
  • spring-data-mongodb
  • Hibernate提供的Hibernate OGM(使用JPA操作NOSQL数据库)