标签:取出 array 能力 怎么办 转换函数 检测 移植 head 工作量
好的数据库结构有利于:节省数据的存储空间,能够保证数据的完整性,方便进行数据库应用系统的开发设计不好的数据库结构将导致:数据冗余、存储空间浪费和内存空间浪费.
数据模型是一种标识实体类型及其实体间联系的模型。典型的数据模型有网状模型、层次模型和关系模型。
从关系数据库的表中,除去冗余数据的过程称为规范化。包括:精简数据库的结构,从表中删除冗余的列,标识所有依赖于其它数据的数据
尽可能使用约束,如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。
高并发环境下锁粒度把控是一门重要的学问。选择一个好的锁,在保证数据安全的情况下,可以大大提升吞吐率,进而提升性能。
如何选择
在乐观锁与悲观锁的选择上面,主要看下两者的区别以及适用场景就可以了。
1??响应效率:如果需要非常高的响应速度,建议采用乐观锁方案,成功就执行,不成功就失败,不需要等待其他并发去释放锁。乐观锁并未真正加锁,效率高。一旦锁的粒度掌握不好,更新失败的概率就会比较高,容易发生业务失败。
2??冲突频率:如果冲突频率非常高,建议采用悲观锁,保证成功率。冲突频率大,选择乐观锁会需要多次重试才能成功,代价比较大。
3??重试代价:如果重试代价大,建议采用悲观锁。悲观锁依赖数据库锁,效率低。更新失败的概率比较低。
4??乐观锁如果有人在你之前更新了,你的更新应当是被拒绝的,可以让用户从新操作。悲观锁则会等待前一个更新完成。这也是区别。
随着互联网三高架构(高并发、高性能、高可用)的提出,悲观锁已经越来越少的被应用到生产环境中了,尤其是并发量比较大的业务场景。
事务基本特性ACID分别是:
原子性指的是一个事务中的操作要么全部成功,要么全部失败。
一致性指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱,假设中间sql执行过程中系统崩溃A也不会损失100块,因为事务没有提交,修改也就不会保存到数据库。
隔离性指的是一个事务的修改在最终提交前,对其他事务是不可见的。
持久性指的是一旦事务提交,所做的修改就会永久保存到数据库中。
而隔离性有4个隔离级别,分别是:
read uncommit 读未提交,可能会读到其他事务未提交的数据,也叫做脏读。
用户本来应该读取到id=1的用户age应该是10,结果读取到了其他事务还没有提交的事务,结果读取结果age=20,这就是脏读。
read commit 读已提交,两次读取结果不一致,叫做不可重复读。
不可重复读解决了脏读的问题,他只会读取已经提交的事务。
用户开启事务读取id=1用户,查询到age=10,再次读取发现结果=20,在同一个事务里同一个查询读取到不同的结果叫做不可重复读。
repeatable read 可重复复读,这是mysql的默认级别,就是每次读取结果都一样,但是有可能产生幻读。
serializable 串行,一般是不会使用的,他会给每一行读取的数据加锁,会导致大量超时和锁竞争的问题。
A原子性由undo log日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql
C一致性一般由代码层面来保证
I隔离性由MVCC来保证
D持久性由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,事务提交的时候通过redo log刷盘,宕机的时候可以从redo log恢复
要说幻读,首先要了解MVCC,MVCC叫做多版本并发控制,实际上就是保存了数据在某个时间节点的快照。
我们每行数实际上隐藏了两列,创建时间版本号,过期(删除)时间版本号,每开始一个新的事务,版本号都会自动递增。
还是拿上面的user表举例子,假设我们插入两条数据,他们实际上应该长这样。
id | name | create_version | delete_version |
---|---|---|---|
1 | 张三 | 1 | |
2 | 李四 | 2 |
这时候假设小明去执行查询,此时current_version=3
select * from user where id<=3;
同时,小红在这时候开启事务去修改id=1的记录,current_version=4
update user set name=‘张三三‘ where id=1;
执行成功后的结果是这样的
id | name | create_version | delete_version |
---|---|---|---|
1 | 张三 | 1 | |
2 | 李四 | 2 | |
1 | 张三三 | 4 |
如果这时候还有小黑在删除id=2的数据,current_version=5,执行后结果是这样的。
id | name | create_version | delete_version |
---|---|---|---|
1 | 张三 | 1 | |
2 | 李四 | 2 | 5 |
1 | 张三三 | 4 |
由于MVCC的原理是查找创建版本小于或等于当前事务版本,删除版本为空或者大于当前事务版本,小明的真实的查询应该是这样
select * from user where id<=3 and create_version<=3 and (delete_version>3 or delete_version is null);
所以小明最后查询到的id=1的名字还是‘张三‘,并且id=2的记录也能查询到。这样做是为了保证事务读取的数据是在事务开始前就已经存在的,要么是事务自己插入或者修改的。
明白MVCC原理,我们来说什么是幻读就简单多了。举一个常见的场景,用户注册时,我们先查询用户名是否存在,不存在就插入,假定用户名是唯一索引。
id | Name | create_version | delete_version |
---|---|---|---|
1 | 张三 | 1 | |
2 | 李四 | 2 | |
3 | 王五 | 7 |
间隙锁是可重复读级别下才会有的锁,结合MVCC和间隙锁可以解决幻读的问题。我们还是以user举例,假设现在user表有几条记录
id | Age |
---|---|
1 | 10 |
2 | 20 |
3 | 30 |
当我们执行:
begin;
select * from user where age=20 for update;
begin;
insert into user(age) values(10); #成功
insert into user(age) values(11); #失败
insert into user(age) values(20); #失败
insert into user(age) values(21); #失败
insert into user(age) values(30); #失败
只有10可以插入成功,那么因为表的间隙mysql自动帮我们生成了区间(左开右闭)
(negative infinity,10],(10,20],(20,30],(30,positive infinity)
由于20存在记录,所以(10,20],(20,30]区间都被锁定了无法插入、删除。
如果查询21呢?就会根据21定位到(20,30)的区间(都是开区间)。
需要注意的是唯一索引是不会有间隙索引的。
首先分库分表分为垂直和水平两个方式,一般来说我们拆分的顺序是先垂直后水平。
垂直分库
基于现在微服务拆分来说,都是已经做到了垂直分库了
垂直分表
如果表字段比较多,将不常用的、数据较大的等等做拆分
水平分表
首先根据业务场景来决定使用什么字段作为分表字段(sharding_key),比如我们现在日订单1000万,我们大部分的场景来源于C端,我们可以用user_id作为sharding_key,数据查询支持到最近3个月的订单,超过3个月的做归档处理,那么3个月的数据量就是9亿,可以分1024张表,那么每张表的数据大概就在100万左右。
比如用户id为100,那我们都经过hash(100),然后对1024取模,就可以落到对应的表上了。
因为我们主键默认都是自增的,那么分表之后的主键在不同表就肯定会有冲突了。有几个办法考虑:
List<Callable<List<User>>> taskList = Lists.newArrayList();
for (int shardingIndex = 0; shardingIndex < 1024; shardingIndex++) {
taskList.add(() -> (userMapper.getProcessingAccountList(shardingIndex)));
}
List<ThirdAccountInfo> list = null;
try {
list = taskExecutor.executeTask(taskList);
} catch (Exception e) {
//do something
}
public class TaskExecutor {
public <T> List<T> executeTask(Collection<? extends Callable<T>> tasks) throws Exception {
List<T> result = Lists.newArrayList();
List<Future<T>> futures = ExecutorUtil.invokeAll(tasks);
for (Future<T> future : futures) {
result.add(future.get());
}
return result;
}
}
首先先了解mysql主从同步的原理
由于mysql默认的复制方式是异步的,主库把日志发送给从库后不关心从库是否已经处理,这样会产生一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了。由此产生两个概念。
全同步复制
主库写入binlog后强制同步日志到从库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。
半同步复制
和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回ACK确认给主库,主库收到至少一个从库的确认就认为写操作完成。
在mysql5.5版本之前,复制都是异步复制。 该复制经常遇到的问题是:因为binlog日志是推送的,所有主库和从库之间存在一定的延迟。 这样就会造成很多问题,比如主库因为磁盘损坏等故障突然崩掉,导致binlog日志不存在,同时因为延迟binlog还没有推送到从库,从库也就会丢失很多被主库提交的事物,从而造成主从不一致。
解决如上的问题,mysql5.5版本之后引入了半同步复制机制。
异步复制:主库写入一个事务commit提交并执行完之后,,将日志记录到binlog,将结果反馈给客户端,最后将日志传输到从库。指的是客户端连接到MySQL主服务器写入一段数据,MySQL主服务器将写入的数据发送给MySQL从服务器, 然后直接返回客户端OK, 可能从服务器的数据会和主服务不一致
同步复制:指的是客户端连接到MySQL主服务器写入一段数据,MySQL主服务器同步给MySQL从服务器需要等待从服务器发出同步完成的响应才返回客户端OK, 这其中等待同步的过程是阻塞的, 如果有N台从服务器, 效率极低。
半同步复制:主库写入一个事务commit提交并执行完之后,并不直接将请求反馈给前端应用用户,而是等待从库也接收到binlog日志并成功写入中继日志后,主库才返回commit操作成功给客户端。半同步复制保障了事物执行后,至少有两份日志记录,一份在主库的binlog上 ,另一份至少在从库的中继日志Relay log上,这样就极大的保证了数据的一致性。
1.错误日志:Error Log. 记录mysql运行过程ERROR,WARING等信息,系统出错或某条记录出问题可查看ERROR日志。
2.日常运行日志:General query log 记录mysql中每条请求数据。
3.二进制日志:Binary log, 包含一些事件,数据库的改动等。
4.慢查询日志:slow query log, 用于mysql的性能调优。
要提高数据库的运行效率,必须从数据库设计级优化、数据库系统级优化、程序实现级优化,这三个层次上同时下功夫。
若发现某个表的字段太多,例如超过八十个,则垂直分割该表,将原来的一个表分解为两个表。
数据库系统级优化
程序实现级优化
在使用面向数据的SQL语言进行程序设计时,尽量采取优化算法。
当计算非常复杂、而且记录条数非常巨大时(例如一千万条),复杂计算要先在数据库外面,以文件系统方式用C++语言计算处理完成之后,最后才入库追加到表中去。
给数据库做索引,合理的索引能立即显著地提高数据库整个系统的性能。
在适当的情况下,尽可能的用存储过程而不是SQL查询。因为前者已经过了预编译,运行速度更快。
优化查询语句,通过高性能的查询语句提高数据库的性能。
常用的sql优化方法
索引的使用
- 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
- 索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,
因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。
一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。- 在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。
查询语句
任何地方都不要使用 select * from t ,用具体的字段列表代替“*”,不要返回用不到的任何字段。
不要写一些没有意义的查询,如需要生成一个空表结构:
select col1,col2 into #t from t where 1=0 这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样: create table #t(...)where条件
应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。
应尽量避免在 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=20in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3很多时候用 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)下面的查询也将导致全表扫描:
select id from t where name like ‘%abc%‘应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100 应改为: select id from t where num=100*2应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)=‘abc‘--name以abc开头的id 应改为:
select id from t where name like ‘abc%‘不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。 .应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询: select id from t where num=0表的字段
- 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。 这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。
- 尽可能的使用 varchar 代替 char ,因为首先变长字段存储空间小,可以节省存储空间, 其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。
临时表的使用
- 避免频繁创建和删除临时表,以减少系统表资源的消耗。
- 临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。
- 如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定
- 在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log , 以提高速度;如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。
游标的使用
- 尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写
- 使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效
- 与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。
在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。尽量避免大事务操作,提高系统并发能力。
尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。
作者:bootaiRocketQ
链接:https://www.jianshu.com/p/d2ac26ca6525
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处
作者:bootaiRocketQ
链接:https://www.jianshu.com/p/359c3fe50c7f
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处
标签:取出 array 能力 怎么办 转换函数 检测 移植 head 工作量
原文地址:https://www.cnblogs.com/zuti666/p/14123691.html