文章目录
一.事务
1.简介
事务主要用于处理操作量大,复杂度高,并且关联性强的数据。
比如:在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务。
在MySQL中只有Innodb存储引擎支持事务。
事务处理可以用来维护数据库的完整性,保证成批的SQL语句要么全部执行,要么全部不执行。主要针对insert,update,delete语句而设置。
2.事务四大特性
在写入或更新资料的过程中,为保证事务是正确可靠的,所必须具备的四个特性:
(1)原子性(Atomicity)
事务中所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。
事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
(2)一致性(Consistency)
在事务开始之前和事务结束之后,数据库的完整性没有被破坏。
这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度,串联性以及后续数据库可以自发性地完成预定的工作。
(3)隔离性(Isolation)
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
事务隔离分为不同级别:
a.读取未提交(read uncommitted)
所有事务都可以看到其他未提交事务的执行结果
本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少
该级别引发的问题是——脏读(Dirty Read):读取到了未提交的数据
b.读提交(read committed)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)
它满足了隔离的简单定义:一个事务只能看见已经提交事务做的改变
这种隔离级别出现的问题是:不可重复读(Nonrepeatable Read):不可重复读意味着我们在同一个事务中执行完全相同的select语句是可能看到不一样的结果。
导致这种情况的原因可能有:
有一个交叉的事务有新的commit,导致了数据的改变;
一个数据库被多个实例操作时,同一事务的其他实例在该实例处理期间可能会有新的commit。
c.可重复读(repeatable read)
这是MySQL的默认事务隔离级别
它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行
此级别可能出现的问题:幻读(Phantom Read):当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的‘幻影’行。
InnoDB通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决幻读问题。
InnoDB还通过间隙锁解决幻读问题。
d.序列化(Serializable)
这是最高的隔离级别,在该级别下,事务串行化顺序执行,可以避免脏读、不可重复读与幻读。
它通过强制事务排序,使之不可能互相冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。
在这个级别,可能导致大量的超时现象和锁竞争,效率低下,比较耗数据库性能,⼀般不使用。
事务隔离级别相关设置:
查看当前事务隔离级别:
--查看全局事务隔离级别
select @@global.transaction_isolation;
--查看当前会话的事务隔离级别
select @@session.transaction_isolation;
通过配置文件设置事务隔离级别:
在 /etc/mysql/my.cnf 文件中写入一下配置
[mysqld]
transaction-isolation = REPEATABLE-READ
设置全局事务隔离级别:
set global transaction isolation level serializable;
设置当前会话的事务隔离级别(仅当前会话,当重启时,会恢复到原全局事务隔离的级别):
set session transaction isolation level read uncommitted;
(4)持久性(Durability)
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
3.语法与使用
开启事务:BEGIN或START TRANSACTION
提交事务: COMMIT,提交会让所有修改生效
回滚: ROLLBACK,撤销正在进行的所有未提交的修改
创建保存点: SAVEPOINT identifier
删除保存点: RELEASE SAVEPOINT identifier
把事务回滚到保存点: ROLLBACK TO identifier
设置事务的隔离级别: SET TRANSACTION
InnoDB 提供的隔离级别有:
READ
UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE
4.示例
首先同时开启两个窗口,连接同一数据库
先在1.阿里云窗口开启事务:BEGIN或START TRANSACTION
修改id=3的成绩:
在2.阿里云窗口中,我们并没有开启事务,我们也来查询,看看会有怎么样的结果:
我们会惊奇的发现,查询出来的数据跟原数据相同,没变。
这是因为在事务里面和事务外面是不一样的,这体现出了事务的隔离性。在事务里面看到的只是事务本身场景里面的数据,并没有提交到数据库里。
那如果在2.阿里云窗口也进行修改数据,会发生什么结果:
我们会发现卡住了,为什么会出现这样的情况?
这是事务的隔离性的体现,因为在1.阿里云窗口的事务并未提交,不允许其他人来修改,防止数据混乱。
一直等待上一事务的提交,若未提交,则会超时报错:
如果我们在1.阿里云窗口中发现修改错误了,想要撤销的话,这里可以使用回滚,将会发现数据恢复了(rollback后,事务自动结束)
正常事务提交:
提交过后,事务自动结束,数据提交到数据库中进行保存。
在2.阿里云窗口也可以进行查看:
二.锁
锁是计算机协调多个进程或线程并发访问某一资源的机制。
锁保证数据并发访问的一致性、有效性;
锁冲突也是影响数据库并发访问性能的一个重要因素。
锁是MySQL在服务器层和存储引擎层的并发控制。
1.分类
MySQL中的锁分为三种:
表级锁、页面锁(现已不再默认支持)、行级锁
MySQL中不同类型的锁的对比:
不同的存储引擎对锁的支持也不一样,详情如下:
注意:BDB(BerkeleyDB)现在已经不再默认提供,所以页锁基本可以忽略。
2.概念详解
(1)表级锁
表级锁是MySQL中锁定粒度最大的一种锁
对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。
特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
表锁分为读锁和写锁两种,详情如下:
1.读锁
用法:
-- 加锁
LOCK TABLES `表名` READ;
...
-- 释放锁
UNLOCK TABLES;
我们在1.阿里云窗口给student表加上读锁:
对表进行查询、修改、删除、插入:
我们会发现,我们只能读,不能进行其他操作。
在2.阿里云窗口中并没有开启事务,我们进行查询、修改、删除、插入:
我们会发现也可以进行查询,但在执行修改、删除、插入时,会等待,等待锁的释放。
这时我们将1.阿里云窗口中的锁释放,会发现2.阿里云窗口的修改语句可执行。
总结:
2.写锁
用法:
-- 加锁
LOCK TABLES `article` WRITE;
...
-- 释放锁
UNLOCK TABLES;
我们在1.阿里云窗口给student表加上写锁:
对表进行插入、查询、修改、删除,我们发现都可以执行:
在2.阿里云窗口中进行插入、查询、修改、删除,我们发现都不能执行。
总结:
3.互斥性
两个并发会话中,读锁之间不互斥,读锁与写锁互斥,两个写锁互斥。
读锁之间不互斥:
读锁与写锁互斥:
两个写锁互斥:
(2)行级锁
介绍:
行级锁是MySQL中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。
行级锁只有InnoDB引擎支持。
行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。
行级锁一般在事务中使用,在事务中,增、删、改的行会自动添加一个行锁,select的行不会自动加锁,如果需要则手动添加。
特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
1.共享锁(读锁)
共享锁又称为读锁、S-锁, 是在事务对⼀行进行读取时添加的。
获得共享锁的事务对该行可读、可写;
其他用户可以并发读取数据,但任何事务都不能对数据进行修改,直到已释放所有共享锁。
其他线程可以对当前行增加共享锁,但不能加排他锁。
用法:
-- 开启事务
begin;
-- 为⼀⾏设置共享锁
SELECT * FROM `student` WHERE id=4 LOCK IN SHARE MODE;
...
-- 提交事务,锁会同时释放
commit;
当1.阿里云窗口没有添加行级锁时,2.阿里云窗口能修改数据:
当1.阿里云窗口添加行级锁时,2.阿里云窗口则不能修改数据:
使用commit进行提交,可释放所有行锁。
排他锁(写锁)
排他锁 又称为 写锁、独占锁、X-锁。
如果事务T对数据A加上排他锁后,则其他事务其他线程对其可读、不可写,不能再对A加任何类型的封锁。
持有排他锁的事务既能读数据,又能修改数据。
用法:
事务中的 update、insert、delete 操作,InnoDB 会自动加排他锁,无需人为干预,select 操作需要手动加锁。
-- 开启事务
begin;
-- 为⼀⾏设置排他锁
SELECT * FROM `user` WHERE id=123 FOR UPDATE;
...
-- 提交事务,锁会同时释放
commit;
3.其他概念
(1)乐观锁(Optimistic Lock)
假设不会发⽣并发冲突,只在提交操作时检查是否违反数据完整性。 乐观锁不能解决脏读的问题。
乐观锁, 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断⼀下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
(2)悲观锁(Pessimistic Lock)
假定会发生并发冲突,屏蔽⼀切可能违反数据完整性的操作。
悲观锁,顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
4.死锁问题
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等的进程称为死锁进程。
(1)创建两个窗口并同时开启两个事务:
(2)首先,我们在1 阿里云窗口更新一条数据,会发现可以执行:
(3)在2 阿里云窗口也更新id不相同的一条数据,会发现也可以执行:
(4)在1 阿里云窗口更新id=7的数据,会发现事务在等待,在等待2 阿里云窗口中锁的释放:
(5)而这时,正巧的是,我们在2 阿里云窗口进行更新id=4的数据,这就需要等待1 阿里云窗口中的锁释放,两个相互等待,这时就会发生死锁:
(6)解决办法:可以在2 阿里云窗口使用 rollback命令,在1 阿里云窗口使用commit进行事务提交,便可以在2 阿里云窗口进行操作了。