一、事务

当一个业务逻辑需要多个sql完成时,如果其中 某条sql语句出错,则希望整个操作都退回。

使用事务可以完成退回的功能,保证 业务逻辑的正确性。

 

事务四大特性 : (简称 ACID )

原子性(Atomicity) : 事务中的全部操作在数据库中是不可分割的,要么全部完成,要么均不执行。

一致性(Consistency): 几个并行执行的事务,其执行结果必须与按某一顺序串行执行的的结果相一致。

隔离性(Isolation) : 事务的执行不受其他事务的干扰,事务执行的中间结果必须对其他事务是透明的。

持久性(Durability) : 对于任意已提交 事务,系统必须保证该事务对数据库的改变不被丢失,即使数据库 出现故障。

 

一个最常见的 事务 : 银行转账

你 给 别人转账,结果 : 你账户的钱减少 , 他账户的钱增加

原子性 : 要么你的钱没减少他的钱没增加 (转账失败,都不执行),要么你的钱减少他的钱增加 (转账成功,都执行了)

一致性 : 是他的钱先增加还是你的钱先减少或者同时发生对 最终 结果没有影响

隔离性 : 你在给别人 转钱,其他人也在给这个人转钱,现在这个交易不受其他交易影响

持久性 : 要保证 这次转账生效。

 

事务的隔离级别:

前面提到,事务的执行不受其他事务影响,中间结果必须对其他事务透明, 怎么实现这个需求呢?

首先我们看下如果没有这个隔离, 可能会带来什么结果.

一、读的问题

1、脏读 : 指一个事务读到了另一个事务未提交的数据。

以银行转账为例, 业务场景如下 :

小明卡内余额 500 元, 小张卡内余额 800 元,小花卡内余额 200 元。

小明给小花转账100, 此时的事务可能是这样

开启事务:

第一步:小明卡内余额减少 100元,余额变成 400 元。(如果失败则回滚, 小明余额不变)

第二步:小花卡内余额增加 100 元, 余额变成 300 元。(如果失败则回滚, 小明和小花的余额都不变)

提交事务成功,小明卡内余额400元, 小花卡内余额300元 。

如果在小明给小花转账的同事, 小张也给小花转账, 步骤跟小明的一样的, 如果碰巧第二步同时发生,

小张在给小花增加100元之前小明给他增加了100元, 那么小张第二部应该是这样的

第二部:小花卡内余额增加 100 元, 余额从300元变成 400 元。(如果失败则回滚, 小张和小花的余额都不变)

此时小张这次事务给小花转账是从 300 元开始算起的, 如果成功, 小花的余额就变成 400 元。

如果小明给小花转账失败,小张给小花转账成功,最终的结果是 : 

小明卡内余额 500 元, 小张卡内余额 700 元,小花卡内余额 400 元。

这样问题就出来了, 小张把可能发生的事情直接当作已经发生去处理。 这个就是脏读

2、不可重复读:指一个事务多次读同一条数据的时候,另一个事务在中间时刻提交对这条数据的修改,导致多次查询结果不一致(主要是单条数据,锁行可以解决)。

 

例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。

原始读取不可重复。如果只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。

3、幻读:指一个事务在按条件查询的时候,另外一个事务新增或者删除了符合条件的数据,导致多次查询结果不一致(侧重于表数据量,锁表可以解决)。

例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主副本时,发现作者已将未编辑的新材料添加到该文档中。

如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。

 

注 : 

不可重复读的重点是修改:同样的条件, 你读取过的数据, 再次读取出来发现值不一样了

幻读的重点在于新增或者删除:同样的条件, 第1次和第2次读出来的记录数不一样

当然, 从总的结果来看, 似乎两者都表现为两次读取的结果不一致,但如果你从控制的角度来看, 两者的区别就比较大:
对于前者, 只需要锁住满足条件的记录
对于后者, 要锁住满足条件及其相近的记录

 

对于上面的三个问题, 不同的隔离级别给出了不同的解决方案和结果。

四大隔离级

读未提交 : 一个事务还没提交时,它做的变更就能被别的事务看到。

读已提交:一个事务提交之后,它做的变更才会被其他事务看到。

可重复读(默认级别):一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的(在第一次查询的时候生成了临时表, 以后查询的是临时表)。

当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。

串行化:对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

 

要使用事务的要求 :、

表的存储引擎 必须是 innodb 或 bdb 类型,才可以对此表使用事事务。

 

查看表的创建语句 :

show create table students;

修改表的 存储引擎 :

alter table '表名' engine=innodb;

事务语句 :

开启 begin ;

提交 commit ;

回滚 rollback ;

例如 : 我需要修改 students 表中 某人 的name【使用默认隔离级别的情况下(可重复读)】 。

1.执行 begin (注意,此时数据库里面这条要被更新的数据已经被锁起来了【锁行】,

其他人不能更改这条数据,要更改就会导致阻塞,但是还是可以查询),

此时会生成一张内存级的临时表,然后将这条数据拷贝到这张临时表里面执行 我们的 sql 语句 ,得到结果。

此时你进行查询,查询的 是临时表 ,别人做查询,查询 的是内存表,所以得到的结果可能会 不一样。

2. 执行 commit , 此时会将临时表中的 数据拷贝到数据库里面的 表 中,

原表的锁被打开。

3.执行 rollback , 此时数据库里面的表的数据会恢复到 begin 之前的状态,

并且内存级的临时表也会消失 ,原表的锁被打开。

需要注意的是 : innodb的行级锁是依赖于索引实现的,where条件后的字段只有是索引字段才能应用行级锁定,否则还会是表级锁

二、索引

索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度。

打个比方,如果合理的设计且使用索引的MySQL是一辆兰博基尼的话,那么没有设计和使用索引的MySQL就是一个人力三轮车。

索引分单列索引和组合索引。

单列索引,即一个索引只包含单个列,一个表可以有多个单列索引,但这不是组合索引。

组合索引,即一个索引包含多个列。

创建索引时,你需要确保该索引是应用在 SQL 查询语句的条件(一般作为 WHERE 子句的条件)。

实际上,索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录。

 

但过多的使用索引将会造成滥用。因此索引也会有它的缺点:虽然索引大大提高了查询速度,

同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,

MySQL不仅要保存数据,还要保存一下索引文件。

建立索引会占用磁盘空间的索引文件。

 

开启时间监测 :

set profiles=1;

查看执行时间 :

show profiles;

查询衡阳市的所有信息:

select * from areas where atitle='衡阳';

给城市创建索引 :

语法 : create index 索引 on 表名(字段名);

为 表areas 的 atitle 列创建索引 :

create index titleIndex on areas(atitle);

再次查询:

select * from areas where atitle='衡阳';

查看创建索引前后查询所需要的时间 :

show profiles;

+----------+------------+----------------------------------------------+

| Query_ID | Duration | Query |

+----------+------------+----------------------------------------------+

| 1 | 0.00013000 | select * from areas |

| 2 | 0.00006600 | select * from areas where atitle='??' |

| 3 | 0.00026500 | select * from areas where atitle='??' |

| 4 | 0.00025300 | show index from areas |

| 5 | 0.00211200 | create index titleIndex on areas(atitle(20)) |

| 6 | 0.00032800 | select * from areas where atitle='??' |

+----------+------------+----------------------------------------------+

当数据量特别大的时候,创建 索引是很有必要的。

 

也可以在创建表的时候直接指定索引 :

CREATE TABLE mytable( ID INT NOT NULL, username VARCHAR(16) NOT NULL, UNIQUE [indexName] (username(length)) //指定索引 );

 

关于索引的 具体情况见以下链接

http://www.runoob.com/mysql/mysql-index.html

总结 : 

创建索引可以认为是在物理内存中给数据添加了一个字典的拼音索引一样的东西 ,以后需要查询数据的时候通过这个索引查找,这样就不用像原来那样一行一行的遍历,

可以跳过许多不相关的数据,从而提高了查询速度,但是如果要对数据进行更改,那么索引也需要跟着 改,所以更改起来会变慢。