时间:2021-07-01 10:21:17 帮助过:37人阅读
好的数据库结构有利于:节省数据的存储空间,能够保证数据的完整性,方便进行数据库应用系统的开发设计不好的数据库结构将导致:数据冗余、存储空间浪费和内存空间浪费.
数据模型是一种标识实体类型及其实体间联系的模型。典型的数据模型有网状模型、层次模型和关系模型。
从关系数据库的表中,除去冗余数据的过程称为规范化。包括:精简数据库的结构,从表中删除冗余的列,标识所有依赖于其它数据的数据
尽可能使用约束,如check,主键,外键,非空字段等来约束,这样做效率最高,也最方便。
其次是使用触发器,这种方法可以保证,无论什么业务访问数据库都可以保证数据的完整新和一致性。
最后考虑的是自写业务逻辑,但这样做麻烦,编程复杂,效率低下。
第一范式:列不可再分
第二范式:行可以唯一区分,主键约束
第三范式:表的非主属性不能依赖与其他表的非主属性 外键约束
且三大范式是一级一级依赖的,第二范式建立在第一范式上,第三范式建立第一第二范式上
主键在本表中是唯一的、不可唯空的,外键可以重复可以唯空;外键和另一张表的主键关联,不能创建对应表中不存在的外键。
明确原始单据与实体之间的关系。可以是一对一、一对多、多对多的关系。
要善于识别与正确处理多对多的关系。
若两个实体之间存在多对多的关系,则应消除这种关系。消除的办法是,在两者之间增加第三个实体。这样,原来一个多对多的关系,现在变为两个一对多的关系。要将原来两个实体的属性合理地分配到三个实体中去。这里的第三个实体,实质上是一个较复杂的关系,它对应一张基本表。
一般来讲,数据库设计工具不能识别多对多的关系,但能处理多对多的关系。〖例3〗:在“图书馆信息系统”中,“图书”是一个实体,“读者”也是一个实体。这两个实体之间的关系,是一个典型的多对多关系:一本图书在不同时间可以被多个读者借阅,一个读者又可以借多本图书。为此,要在二者之间增加第三个实体,该实体取名为“借还书”,它的属性为:借还时间、借还标志(0表示借书,1表示还书),另外,它还应该有两个外键(“图书”的主键,“读者”的主键),使它能与“图书”和“读者”连接。
主键与外键。主键是实体的高度抽象,主键与外键的配对,表示实体之间的连接。
主键Prime Key的取值方法
PK是供程序员使用的表间连接工具,可以是一无物理意义的数字串, 由程序自动加1来实现。也可以是有物理意义的字段名或字段名的组合。不过前者比后者好。当PK是字段名的组合时,建议字段的个数不要太多,多了不但索引占用空间大,而且速度也慢。
基本表的性质。
基本表与中间表、临时表不同,因为它具有如下四个特性:
1)原子性。基本表中的字段是不可再分解的。
2)原始性。基本表中的记录是原始数据(基础数据)的记录。
3)演绎性。由基本表与代码表中的数据,可以派生出所有的输出数据。
4)稳定性。基本表的结构是相对稳定的,表中的记录是要长期保存的。
理解基本表的性质后,在设计数据库时,就能将基本表与中间表、临时表区分开来。
中间表、报表和临时表。
中间表是存放统计数据的表,它是为数据仓库、输出报表或查询结果而设计的,有时它没有主键与外键(数据仓库除外)。
临时表是程序员个人设计的,存放临时记录,为个人所用。
基表和中间表由DBA维护,临时表由程序员自己用程序自动维护。
视图技术在数据库设计中很有用。
与基本表、代码表、中间表不同,视图是一种虚表,它依赖数据源的实表而存在。视图是供程序员使用数据库的一个窗口,是基表数据综合的一种形式, 是数据处理的一种方法,是用户数据保密的一种手段。
为了进行复杂处理、提高运算速度和节省存储空间, 视图的定义深度一般不得超过三层。 若三层视图仍不够用, 则应在视图上定义临时表, 在临时表上再定义视图。这样反复交迭定义, 视图的深度就不受限制了。
对于某些与国家政治、经济、技术、军事和安全利益有关的信息系统,视图的作用更加重要。这些系统的基本表完成物理设计之后,立即在基本表上建立第一层视图,这层视图的个数和结构,与基本表的个数和结构是完全相同。并且规定,所有的程序员,一律只准在视图上操作。只有数据库管理员,带着多个人员共同掌握的“安全钥匙”,才能直接在基本表上操作
三个范式标准。在数据库设计中,为了更好地应用三个范式,就必须通俗地理解三个范式(通俗地理解是够用的理解,并不是最科学最准确的理解):
第一范式:1NF是对属性的原子性约束,要求属性具有原子性,不可再分解;
第二范式:2NF是对记录的惟一性约束,要求记录有惟一标识,即实体的惟一性;
第三范式:3NF是对字段冗余性的约束,即任何字段不能由其他字段派生出来,它要求字段没有冗余。没有冗余的数据库设计可以做到。但是,没有冗余的数据库未必是最好的数据库,有时为了提高运行效率,就必须降低范式标准,适当保留冗余数据。
具体做法是:在概念数据模型设计时遵守第三范式,降低范式标准的工作放到物理数据模型设计时考虑。降低范式就是增加字段,允许冗余。
正确认识数据冗余.
主键与外键在多表中的重复出现, 不属于数据冗余,这个概念必须清楚,事实上有许多人还不清楚。非键字段的重复出现, 才是数据冗余!而且是一种低级冗余,即重复性的冗余。高级冗余不是字段的重复出现,而是字段的派生出现。
〖例4〗:商品中的“单价、数量、金额”三个字段,“金额”就是由“单价”乘以“数量”派生出来的,它就是冗余,而且是一种高级冗余。冗余的目的是为了提高处理速度。只有低级冗余才会增加数据的不一致性,因为同一数据,可能从不同时间、地点、角色上多次录入。因此,我们提倡高级冗余(派生性冗余),反对低级冗余(重复性冗余)。
E--R图没有标准答案.
信息系统的E--R图没有标准答案,因为它的设计与画法不是惟一的,只要它覆盖了系统需求的业务范围和功能内容,就是可行的。反之要修改E--R图。尽管它没有惟一的标准答案,并不意味着可以随意设计。
好的E—R图的标准是:结构清晰、关联简洁、实体个数适中、属性分配合理、没有低级冗余。
完整性约束表现在三个方面
域的完整性:用Check来实现约束,在数据库设计工具中,对字段的取值范围进行定义时,有一个Check按钮,通过它定义字段的值城。
参照完整性:用PK、FK、表级触发器来实现。
用户定义完整性:它是一些业务规则,用存储过程和触发器来实现。
防止数据库设计打补丁的方法是“三少原则”。
2)一个表中组合主键的字段个数越少越好。因为主键的作用,一是建主键索引,二是做为子表的外键,所以组合主键的字段个数少了,不仅节省了运行时间,而且节省了索引存储空间;
数据库设计的实用原则是:在数据冗余和处理速度之间找到合适的平衡点。“三少”是一个整体概念,综合观点,不能孤立某一个原则。该原则是相对的,不是绝对的。
提倡“三少”原则,是叫读者学会利用数据库设计技术进行系统的数据集成。数据集成的步骤是将文件系统集成为应用数据库,将应用数据库集成为主题数据库,将主题数据库集成为全局综合数据库。集成的程度越高,数据共享性就越强,信息孤岛现象就越少,整个企业信息系统的全局E—R图中实体的个数、主键的个数、属性的个数就会越少。
提倡“三少”原则的目的,是防止读者利用打补丁技术,不断地对数据库进行增删改,使企业数据库变成了随意设计数据库表的“垃圾堆”,或数据库表的“大杂院”,最后造成数据库中的基本表、代码表、中间表、临时表杂乱无章,不计其数,导致企事业单位的信息系统无法维护而瘫痪。
提高数据库运行效率的办法。
在给定的系统硬件和系统软件条件下,提高数据库系统的运行效率的办法是:
1)在数据库物理设计时,降低范式,增加冗余, 少用触发器, 多用存储过程。
2)发现某个表的记录太多,例如超过一千万条,则要对该表进行水平分割。水平分割的做法是,以该表主键PK的某个值为界线,将该表的记录水平分割为两个表。若发现某个表的字段太多,例如超过八十个,则垂直分割该表,将原来的一个表分解为两个表。
3)对数据库管理系统DBMS进行系统优化,即优化各种系统参数,如缓冲区个数。
在使用面向数据的SQL语言进行程序设计时,尽量采取优化算法。
当计算非常复杂、而且记录条数非常巨大时(例如一千万条),复杂计算要先在数据库外面,以文件系统方式用C++语言计算处理完成之后,最后才入库追加到表中去。这是电信计费系统设计的经验。
总之,要提高数据库的运行效率,必须从数据库系统级优化、数据库设计级优化、程序实现级优化,这三个层次上同时下功夫。
SQL是Structed Query Language的英文缩写及结构化查询语言,适用于绝大多数关系型数据库。结构化查询语言是关系,数据库语言用于建立存储修改监督和管理关系这个是一种过程化语言数据库的数据。
PL/SQL是Produced Query Language 的缩写,为Oracle数据库所独有。 PL/SQL是一种过程化语言,是oracle对关系数据库语言SQL的过程化扩充。
PL/SQL程序由块结构构成,在PL/SQL中含有变量,各种不同的程序控制结构,异常处理模块、子程序(过程,函数,包)触发器等。
1)字符串类型 char、nchar、varchar、varchar2、nvarchar2
2)数字类型 number、integer
3)浮点类型 binary_float、binary_double、float
4)日期类型 date、 timestamp 5)LOB类型 blob、clob、nclob、bfile
Char的长度是固定的,而varchar2的长度是可以变化的,比如,存储字符串“abc”对于char(20),表示你存储的字符将占20个字节,包含17个空,而同样的varchar2(20)只占了3个字节,20只是最大值,当你存储的字符小于20时,按实际长度存储。 char的效率要比varchar2的效率高。
目前varchar是varchar2的同义词,工业标准的varchar类型可以存储空字符串,但是oracle不能这样做,尽管它保留以后这样做的权利。Oracle自己开发了一个数据类型varchar2,这个类型不是一个标准的varchar,他将在数据库中varchar列可以存储空字符串的特性改为存储null值,如果你想有向后兼容的能力,oracle建议使用varchar2而不是varchar
TRUNCATE TABLE 在功能上与不带 WHERE 子句的 DELETE 语句相同:二者均删除表中的全部行。但 TRUNCATE TABLE 比 DELETE 速度快,且使用的系统和事务日志资源少。
DELETE 语句每次删除一行,并在事务日志中为所删除的每行记录一项。TRUNCATE TABLE 通过释放存储表数据所用的数据页来删除数据,并且只在事务日志中记录页的释放。
TRUNCATE,DELETE,DROP 放在一起比较:
DELETE TABLE: 删除内容不删除定义,不释放空间。
TRUNCATE TABLE :删除内容、释放空间但不删除定义。
DROP TABLE :删除内容和定义,释放空间。
使用distinct关键字.Distinct关键字用于取消完全重复的结果行
只用于数值型数据的列函数
列函数 | 功能描述 |
---|---|
Sum( column ) | 列中所有值的总和 |
Avg (column) | 列中所有值的平均数 |
StdDev(column) | 列的标准偏差 |
Variance(column) | 列的方差 |
用于字符、数值、日期型数据的列函数
列函数 | 功能描述 |
---|---|
Max (column) | 列中的最大值 |
Min (column) | 列中的最小值 |
Count( **) | 列中行的总数 |
Count(column) | 列不为null的行数 |
Count(distinct column) | Column 指定列中相异的数量 |
算术比较条件
比较条件 | 功能描述 | 例子 |
---|---|---|
= | 等于 | Name = “wag” |
> | 大于 | Bonus > 600 |
>= | 大于等于 | Bonus >= 600 |
< | 小于 | hire_date > ‘06-7月-2014’ |
<= | 小于等于 | Bonus <= 800 |
<> ,!= | 不等于 | Bonus <> 800 |
包含
比较条件 | 功能描述 | 例子 |
---|---|---|
In | 在指定集合中 | department_id IN (101,203) |
Not In | 不再指定集合中 | department_id NOT IN (101,203) |
范围
比较条件 | 功能描述 | 例子 |
---|---|---|
Between And | 在指定范围内 | Wage Between 1000 And 3000 |
Not Between And | 不在指定范围内 | Bonus Not Between 1000 And 3000 |
匹配
比较条件 | 功能描述 | 例子 |
---|---|---|
Like | 与指定模式匹配 | Wage |
Not Like | 不与指定模式匹配 |
NULL
比较条件 | 功能描述 | 例子 |
---|---|---|
Is Null | 是Null值 | hire_date Is Null |
Is Not Null | 不是Null值 | hire_date Is Not Null |
逻辑运算符
比较条件 | 功能描述 | 例子 |
---|---|---|
And | 逻辑与运算符 | Bonu >= 600 AND Name LIKE ‘王%’ |
Or | 逻辑或运算符 | Bonu >= 600 OR Name LIKE ‘王%’ |
Not | 逻辑非运算符 | Not Bonu >= 600 |
Group By 可以在表中达到数据分组的目的。
expression 用于指定分组表达式,可以指定一个或多个表达式作为分组依据;conditions 用于指定限制分组结果的条件。
Order By expression ASC | DESC
ASC关键字指定升序排列 ,Desc 关键字指定降序排列
expression可以指定一个以上列或表达式,查询结果首先按着指定的第一列排序,然后根据指定的第二列。。。。。。
多行比较符
比较符 | 作用 |
---|---|
In | 匹配与子查询结果的任意一个值,结果为真;否则为假。 |
Any | 只要符合的查询结果的任意一个值,结果为真;否则为假。 |
All | 符合子查询结果的所有值结果为真;否则为假。 |
集合操作符
集合操作符 | 作用 |
---|---|
Union | 用于得到两个查询结果集的并集并集中自动去掉重复行。 |
Union All | 用于得到两个查询结果集的并集并集中保留重复行。 |
Intersect | 用于得到两个查询结果集的交集,交集按结果的第1列排序。 |
Minus | 用于得到两个查询结果集的差集,差集按结果的第1列进行排序。 |
To_Char函数可以将数字型数据或日期型数据转为文本型数据
内连接:只有两个元素表相匹配的才能在结果集中显示。
外连接:
左外连接:左边为驱动表,驱动表的数据全部显示,匹配表的不匹配的不会显示。
右外连接:右边为驱动表,驱动表的数据全部显示,匹配表的不匹配的不会显示。
全外连接:连接的表中不匹配的数据全部会显示出来。
交叉连接: 笛卡尔效应,显示的结果是链接表数的乘积。
标准SQL外连接查询语句
SELECT <table_name1.* / table_name1.column_name1 ,... table2_name2.*/table_name2.column_name1,...>
FROM table_name1 [LEFT|RIGHT|FULL]
JOIN table_name2 ON conditions
其中,ON子句用于指定连接条件;FROM子句指定外连接类型,
事务:事务(Transaction)是并发控制的基本单元,事务是一个操作序列,要么都执行,要么都不执行,他是一个不可分割的工作单位,事务是维护数据库一致性的单位。事务就是被绑定在一起作为一个逻辑工作单元的SQL语句分组,如果任何一个语句操作失败那么整个操作就被失败,以后操作就会回滚到操作前状态,或者是上有个节点。
四个基本性质ACID:
1.原子性:要么都执行,要么都不执行。
2.一致性:合法的数据才可以被写入。
3.隔离性:允许多个用户并发访问。
4.持久性:事务结束后,事务处理的结果必须得到固化。即一旦提交,对数据库改变是永久的。
事物的语句:
1.提交事务:COMMIT 。结束当前事务,并把当前事务所执行的全部修改,保持到外存的数据库中。同时该命令还删除事务所设置的全部保留点释放该事物的封锁。 在未提交前你前面的操作更新的都是内存,没有更新到物理文件中,执行commit从用户角度讲就是更新到物理文件了,事实上commit时还没有写date file,而是记录了redo log file,要从内存写到data物理文件,需要触发检查点,由DBWR这个后台进程来写
2.回滚事务:ROLLBACK 。撤销当前事物中所做的修改。
3.保留点的设置 ,SAVEPOINT savepoint_name;。用于标识事物中的一个保留点,可回退到该点保留点。与RollBack可以一起使用,可部分撤销当前事物。可以利用保留点进行程序查错和调试。
隐式处理事务
通常我们对数据库进行了误操作时, 需要把数据库Rollback到之前的版本.一个常用的方法就是使用日志来进行数据库恢复. 这个方法虽然强大有效, 但是花费时间等成本高.Oracle提供了另1个快速数据库恢复机制, 就是Flashback.
Flashback的简单原理
Oracle会将数据库数据的每1个改动记录在日志文件中, 所以理论上依靠日志文件, 是能将数据库回滚到任何一个时间点的.
而Flashback的机制有点类似与回收站, 会把数据库改动前的镜像放到undo表空间中.
如果用户要rollback1个数据库对象, 只需要找到undo表空间中对应的Undo数据即可.
Flashback的优点
很明显, Flashback并不依赖于日志文件, 只需Undo表空间中undo数据即可发挥作用.
所以Flashback可以满足用户逻辑错误的快速恢复.
所以优点如下:
快速
在线恢复, 无需关闭数据库
操作简单.便捷.
数值函数
字符函数
日期时间函数
转换函数
索引就一种特殊的查询表,数据库的搜索可以利用它加速对数据的检索。
它很类似与现实生活中书的目录,不需要查询整本书内容就可以找到想要的数据。索引可以是唯一的,创建索引允许指定单个列或者是多个列。
缺点是它减慢了数据录入的速度,同时也增加了数据库的尺寸大小。
唯一、不为空、经常被查询的字段
对一个表格的索引越多,数据库引擎用来更新、插入或者删除数据所需要的时间就越多,因为在数据操控发生的时候索引也必须要维护。
通常,通过索引查询数据比全表扫描要快。
但是我们也必须注意到它的代价. 索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时,索引本身也会被修改. 这意味着每条记录的INSERT,DELETE,UPDATE将为此多付出4,5 次的磁盘I/O.。因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。
使用索引查询不一定能提高查询性能,
索引范围查询(INDEX RANGE SCAN)适用于两种情况:
? 基于一个范围的检索,一般查询返回结果集小于表中记录数的30%宜采用;
? 基于非唯一性索引的检索
索引就是为了提高查询性能而存在的,如果在查询中索引没有提高性能,只能说是用错了索引,或者讲是场合不同
逻辑上:
Single column 单行索引 Concatenated 多行索引
Unique 唯一索引 NonUnique 非唯一索引
Function-based 函数索引
Domain 域索引 物理上:
Partitioned 分区索引 NonPartitioned 非分区索引
B-tree :
Normal 正常型B树 Rever Key 反转型B树 Bitmap 位图索引
视图:是一种虚拟的表,具有和物理表相同的功能。可以对视图进行增,改,查,操作,试图通常是有一个表或者多个表的行或列的子集。对视图的修改会影响基本表。它使得我们获取数据更容易,相比多表查询。
视图其实就是一条查询sql语句,用于显示一个或多个表或其他视图中的相关数据。
表就是关系数据库中实际存储数据用的。
优点:
1)对数据库的访问,因为视图可以有选择性的选取数据库里的一部分。
2 )用户通过简单的查询可以从复杂查询中得到结果。
3 )维护数据的独立性,试图可从多个表检索数据。
4 )对于相同的数据可产生不同的视图。 缺点: 性能:查询视图时,必须把视图的查询转化成对基本表的查询,如果这个视图是由一个复杂的多表查询所定义,那么,那么就无法更改数据
一个完整的块结构包括定义部分,执行部分,异常处理部分三个部分
DECLARE
Declarations
BEGIN
Executable code
EXCEPTION
Exceptional handles
END;
游标:是对查询出来的结果集作为一个单元来有效的处理。游标可以定在该单元中的特定行,从结果集的当前行检索一行或多行。可以对结果集当前行做修改。一般不使用游标,但是需要逐条处理数据的时候,游标显得十分重要。
存储过程是一个预编译的SQL语句,优点是允许模块化的设计,就是说只需创建一次,以后在该程序中就可以调用多次。如果某次操作需要执行多次SQL,使用存储过程比单纯SQL语句执行要快。 调用: 1)可以用一个命令对象来调用存储过程。 2)可以供外部程序调用,比如:java程序。
优点: 1)存储过程是预编译过的,执行效率高。 2)存储过程的代码直接存放于数据库中,通过存储过程名直接调用,减少网络通讯。 3)安全性高,执行存储过程需要有一定权限的用户。 4)存储过程可以重复使用,可减少数据库开发人员的工作量。 缺点:移植性差
存储过程 | 函数 |
---|---|
用于在数据库中完成特定的操作或者任务(如插入、删除等) | 用于特定的数据(如选择) |
程序头部声明用procedure | 程序头部声明用function |
程序头部声明时不需描述返回类型 | 程序头部声明时要描述返回类型,而且PL/SQL块中至少要包括一个有效的return语句 |
可以使用in/out/in out 三种模式的参数 | 可以使用in/out/in out 三种模式的参数 |
可作为一个独立的PL/SQL语句来执行 | 不能独立执行,必须作为表达式的一部分调用 |
可以通过out/in out 返回零个或多个值 | 通过return语句返回一个值,且改值要与声明部分一致,也可以是通过out类型的参数带出的变量 |
SQL语句(DML 或SELECT)中不可调用存储过程 | SQL语句(DML 或SELECT)中可以调用函数 |
触发器是一中特殊的存储过程,主要是通过事件来触发而被执行的。它可以强化约束,来维护数据的完整性和一致性,可以跟踪数据库内的操作从而不允许未经许可的更新和变化。可以联级运算。如,某表上的触发器上包含对另一个表的数据操作,而该操作又会导致该表触发器被触发。
事前触发器运行于触发事件发生之前,而事后触发器运行于触发事件发生之后。通常事前触发器可以获取事件之前和新的字段值。
语句级触发器可以在语句执行前或后执行,而行级触发在触发器所影响的每一行触发一次。
1.冷备份是发生在数据库正常关闭的情况下,当正常关闭时会提供给我们一个完整的数据库。.热备份是在数据库运行的情况下,采用archivelog mode方式来备份数据库的方法。
2 冷备份时将要害性文件拷贝到另一个位置的一种说法。
1??本质的区别
2??数据库安全性
3??SQL语法的区别
Oracle 的 SQL 语法与 MySQL 有很大不同。Oracle 为称为 PL/SQL 的编程语言提供了更大的灵活性。Oracle 的 SQL*Plus 工具提供了比 MySQL 更多的命令,用于生成报表输出和变量定义。
4??存储上的区别
与 Oracle 相比,MySQL 没有表空间、角色管理、快照、同义词和包以及自动存储管理。
5??对象名称的区别
虽然某些模式对象名称在 Oracle 和 MySQL 中都不区分大小写,例如列、存储过程和索引等。但在某些情况下,两个数据库之间的区别大小写是不同的:
6??运行程序和外部程序支持
7??MySQL和Oracle的字符数据类型比较
两个数据库中支持的字符类型存在一些差异。
8??MySQL和Oracle的额外功能比较
9??临时表的区别:Oracle和MySQL以不同方式处理临时表。
1??0??MySQL和Oracle中的备份类型
1??1??Oracle和MySQL的数据库管理
在数据库管理部分,Oracle DBA 比 MySQL DBA 更有收益。与 MySQL 相比,Oracle DBA 有很多可用的范围。
select * from table limit firstIndex,pageSize`
myisam引擎是5.1版本之前的默认引擎,支持全文检索、压缩、空间函数等,但是不支持事务和行级锁,所以一般用于有大量查询少量插入的场景来使用,而且myisam不支持外键,并且索引和数据是分开存储的。
innodb是基于聚簇索引建立的,和myisam相反它支持事务和行级锁、外键,并且通过MVCC来支持高并发,索引和数据存储在一起。
总结:
MYIASM管理非事务表,提供高速存储和检索,以及全文搜索能力,如果在应用中执行大量的select操作,应选择MYIASM引擎
Innodb用于事务处理,具有ACID事务支持等特性,如果在应用中执行大量的insert和update操作,应选择innodb引擎。
索引按照数据结构来说主要包含B+树和Hash索引。
假设我们有张表,结构如下:
create table user(
id int(11) not null,
age int(11) not null,
primary key(id),
key(age)
);
B+树是左小右大的顺序存储结构,节点只包含id索引列,而叶子节点包含索引列和数据,这种数据和索引在一起存储的索引方式叫做聚簇索引,一张表只能有一个聚簇索引。假设没有定义主键,InnoDB会选择一个唯一的非空索引代替,如果没有的话则会隐式定义一个主键作为聚簇索引。
这是主键聚簇索引存储的结构,那么非聚簇索引的结构是什么样子呢?非聚簇索引(二级索引)保存的是主键id值,这一点和myisam保存的是数据地址是不同的。
最终,我们一张图看看InnoDB和Myisam聚簇和非聚簇索引的区别
MySQL 的基本存储结构是页(记录都存在页里边):
每个数据页都会为存储在它里边的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的记录。
所以说,如果写select * from tab where name = ‘xxx‘
这样没有进行任何优化的 sql 语句,默认会这样做:
很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度为O(n)。
索引做了什么可以让查询加快速度呢?其实就是将无序的数据变成有序(相对):
要找到id为8的记录简要步骤:
很明显:没有用索引是需要遍历双向链表来定位对应的页,现在通过“目录”就可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn))。其实底层结构就是B+树,B+树作为树的一种实现,能够很快地查找出对应的记录。
覆盖索引指的是在一次查询中,如果一个索引包含或者说覆盖所有需要查询的字段的值,我们就称之为覆盖索引,而不再需要回表查询。
而要确定一个查询是否是覆盖索引,我们只需要explain sql语句看Extra的结果是否是“Using index”即可。
以上面的user表来举例,我们再增加一个name字段,然后做一些查询试试。
explain select * from user where age=1; //查询的name无法从索引数据获取
explain select id,age from user where age=1; //可以直接从索引获取
mysql锁分为共享锁和排他锁,也叫做读锁和写锁。读锁是共享的,可以通过lock in share mode实现,这时候只能读不能写。写锁是排他的,它会阻塞其他的写锁和读锁。
从颗粒度来区分,可以分为表锁和行锁两种。表锁会锁定整张表并且阻塞其他用户对该表的所有读写操作,比如alter修改表结构的时候会锁表。
行锁又可以分为乐观锁和悲观锁,悲观锁可以通过for update实现,乐观锁则通过版本号实现。
在 Mysql 中,行级锁并不是直接锁记录,而是锁索引。索引分为主键索引和非主键索引两种,如果一条sql 语句操作了主键索引,Mysql 就会锁定这条主键索引;如果一条语句操作了非主键索引,MySQL会先锁定该非主键索引,再锁定相关的主键索引。
InnoDB 行锁是通过给索引项加锁实现的,如果没有索引,InnoDB 会通过隐藏的聚簇索引来对记录加锁。也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表锁一样。因为没有了索引,找到某一条记录就得扫描全表,要扫描全表,就得锁定表。
共享锁又称为读锁,简称S锁,顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。
排他锁又称为写锁,简称X锁,顾名思义,排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据就行读取和修改。
对于共享锁大家可能很好理解,就是多个事务只能读数据不能改数据,排他锁指的是一个事务在一行数据加上排他锁后,其他事务不能再在其上加其他的锁。
mysql InnoDB引擎默认的修改数据语句,update,delete,insert都会自动给涉及到的数据加上排他锁,
select语句默认不会加任何锁类型,
加共享锁可以使用select ... lock in share mode语句。对某一资源加共享锁,自身可以读该资源,其他人也可以读该资源(也可以再继续加共享锁,即 共享锁可多个共存),但无法修改。要想修改就必须等所有共享锁都释放完之后
加排他锁可以使用select ...for update语句,加过排他锁的数据行对某一资源加排他锁,自身可以进行增删改查,其他人无法进行任何操作,在其他事务种是不能修改数据的,也不能通过for update和lock in share mode锁的方式再加锁查询数据,但可以直接通过select ...from...查询数据,因为普通查询没有任何锁机制。
并发控制
当程序中可能出现并发的情况时,就需要通过一定的手段来保证在并发情况下数据的准确性,通过这种手段保证了当前用户和其他用户一起操作时,所得到的结果和他单独操作时的结果是一样的。这种手段就叫做并发控制。并发控制的目的是保证一个用户的工作不会对另一个用户的工作产生不合理的影响。
没有做好并发控制,就可能导致脏读、幻读和不可重复读等问题。
常说的并发控制,一般都和数据库管理系统(DBMS)有关。在 DBMS 中的并发控制的任务,是确保在多个事务同时存取数据库中同一数据时,不破坏事务的隔离性、一致性和数据库的统一性。
实现并发控制的主要手段大致可以分为乐观并发控制和悲观并发控制两种。
悲观锁和乐观锁都是一种思想,java中的锁也有这个思想。
观锁比较适用于读多写少的情况(多读场景),悲观锁比较适用于写多读少的情况(多写场景)。
悲观锁
理解
悲观锁假设数据会发生冲突,这也就是悲观的含义。之所以叫做悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式。总是假设最坏的情况,每次读取数据的时候都默认其他线程会更改数据,因此需要进行加锁操作,当其他线程想要访问数据时,都需要阻塞挂起。
实现:
说明
悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。但是在效率方面,处理加锁的机制会让数据库产生额外的开销,还有增加产生死锁的机会。另外还会降低并行性,一个事务如果锁定了某行数据,其他事务就必须等待该事务处理完才可以处理那行数据。
乐观锁
理解
乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。乐观锁适用于读操作多的场景,这样可以提高程序的吞吐量。
乐观锁机制采取了更加宽松的加锁机制。乐观锁是相对悲观锁而言,也是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性
实现
说明
乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。
具体实现
1??悲观锁的实现,往往依靠数据库提供的锁机制。在数据库中,悲观锁的流程如下:
拿比较常用的 MySql Innodb 引擎举例,来说明一下在 SQL 中如何使用悲观锁。
要使用悲观锁,必须关闭 MySQL 数据库的自动提交属性。因为 MySQL 默认使用 autocommit 模式,也就是说,当执行一个更新操作后,MySQL 会立刻将结果进行提交。(sql语句:set autocommit=0)
2??乐观锁实现方式使用乐观锁就不需要借助数据库的锁机制了。
主要就是两个步骤:冲突检测和数据更新。其实现方式有一种比较典型的就是 CAS(Compare and Swap)。
CAS 是项乐观锁技术,当多个线程尝试使用 CAS 同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。比如前面的扣减库存问题,通过乐观锁可以实现如下:
乐观锁使用
在更新之前,先查询一下库存表中当前库存数(quantity),然后在做 update 的时候,以库存数作为一个修改条件。当提交更新的时候,判断数据库表对应记录的当前库存数与第一次取出来的库存数进行比对,如果数据库表当前库存数与第一次取出来的库存数相等,则予以更新,否则认为是过期数据。
以上更新语句存在一个比较严重的问题,即传说中的ABA问题:
①比如说线程one从数据库中取出库存数3,这时候线程two也从数据库中取出库存数3,并且线程two进行了一些操作变成了2。
②然后线程two又将库存数变成3,这时候线程one进行 CAS 操作发现数据库中仍然是3,然后线程one操作成功。
③尽管线程one的 CAS 操作成功,但是不代表这个过程就是没有问题的。
有一个比较好的办法可以解决 ABA 问题,那就是通过一个单独的可以顺序递增的 version 字段。优化如下:
乐观锁每次在执行数据修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现 ABA 问题,因为版本号只会增加不会减少。除了 version 以外,还可以使用时间戳,因为时间戳天然具有顺序递增性。
以上 SQL 其实还是有一定的问题的,就是一旦遇上高并发的时候,就只有一个线程可以修改成功,那么就会存在大量的失败。对于像淘宝这样的电商网站,高并发是常有的事,总让用户感知到失败显然是不合理的。所以,还是要想办法减少乐观锁的粒度。有一条比较好的建议,可以减小乐观锁力度,最大程度的提升吞吐率,提高并发能力!如下:
以上 SQL 语句中,如果用户下单数为1,则通过quantity - 1 > 0
的方式进行乐观锁控制。在执行过程中,会在一次原子操作中查询一遍 quantity 的值,并将其扣减掉1。
高并发环境下锁粒度把控是一门重要的学问。选择一个好的锁,在保证数据安全的情况下,可以大大提升吞吐率,进而提升性能。
如何选择