想要知道和学习数据库中的锁,要先学习数据库的事务和并发事务所带来的问题!
1.数据库中的事务!
1.1什么事务
事务是由一组SQL语句组成的逻辑处理单元(多个sql进行修改,新增等),这些操作要么同时成功,要么全部回滚,在数据库中,事务是保证数据一致性和完整性得重要机制之一。
事务由事务开始(
begin transaction
)和事务结束(end transaction
)之间执行的全部操作组成
对于 MySQL 数据库来说,事务是指以执行start transaction命令开始,到执行commit或者rollback命令结束之间的全部 SQL 操作,如果这些 SQL 操作全部执行成功,则执行commit命令提交事务,表示事务执行成功;如果这些 SQL 操作中任一操作执行失败,则执行rollback命令回滚事务,表示事务执行失败,并将数据库回滚到执行start transaction命令之前的状态。特别地,在现阶段的 MySQL 数据库中,仅 InnoDB 和 NDB 两个存储引擎是支持事务的。
以 MySQL 的 InnoDB 存储引擎为例,其默认是开启autocommit配置的,即自动提交事务。在自动提交模式下,如果没有以start transaction显式地开始一个事务,那么每条 SQL 语句都会被当做一个事务执行提交操作。通过set autocommit = 0命令可以关闭自动提交模式,如果关闭了autocommit,则所有的 SQL 语句都在一个事务中,直到执行commit或rollback,该事务结束,并同时开始另外一个新的事务。在此,需要我们注意的是,autocommit参数是针对连接的,在一个连接中修改了参数,不会对其他连接产生影响。
注意:除此之外,在 MySQL 中,还存在一些特殊的命令,如果在事务中执行了这些命令,则会强制执行commit命令提交事务,如 DDL 语句(create table/drop table/alter table)、lock tables语句等。不过,我们常用的select、insert、update和delete命令,都不会强制提交事务。
1.2数据库事务的基本演示
演示:
常见的数据库事务操作命令:
BEGIN - 用于开始一个事务。
COMMIT - 用于提交事务。这将使所有已执行的更改永久保存到数据库中。
ROLLBACK - 用于回滚事务。如果事务出现错误或需要取消,这将撤消已执行的所有更改。
SAVEPOINT - 使用SAVEPOINT创建一个保存点,可以在回滚事务时使用它来恢复到该保存点。
ROLLBACK TO SAVEPOINT - 恢复事务到指定的保存点。
SET TRANSACTION - 用于控制事务的特性,例如隔离级别和读取模式。
LOCK TABLE - 用于锁定表,以确保在事务期间没有其他用户对其进行更改。
UNLOCK TABLE - 用于解锁表,以允许其他用户对其进行更改
建表语句:
CREATE TABLE my_table (
id INT PRIMARY KEY,
name VARCHAR(50),
birth_date DATE
);
开启事务;回滚事务;这样该执行的语句就会全部执行失败!
BEGIN;
INSERT INTO my_table (name, birth_date) VALUES
( 'hua444', '1990-07-01');
INSERT INTO my_table (name, birth_date) VALUES
( 'hua44', '1990-07-01');
ROLLBACK ;
开启事务;提交事务;这样所有语句只要都是成功的就会全部执行成功!,有一条执行失败,就全部回滚!
特别简单(执行失败情况)
一条SQL语句错误,全部回滚!
SAVEPOINT 演示
用于创建一个保存点(savepoint)。使用 SAVEPOINT 可以在事务执行过程中创建保存点,并在后续操作中回滚到该保存点
当事务包含多个步骤时,可以使用 SAVEPOINT 将其分为更小的部分,并在每个步骤之间创建保存点。如果后续步骤出现错误,可以回滚到之前的保存点,避免取消整个事务。
ROLLBACK TO SAVEPOINT 用于回滚到指定的保存点。使用 ROLLBACK TO SAVEPOINT 可以在事务执行过程中撤销已完成的操作,并返回到指定的保存点。
当事务包含多个步骤时,可以使用 SAVEPOINT 将其分为更小的部分,并在每个步骤之间创建保存点。如果后续步骤出现错误,可以使用 ROLLBACK TO SAVEPOINT 命令将事务恢复到之前的保存点,避免取消整个事务。
使用 ROLLBACK TO SAVEPOINT 时,需要注意以下几点:
如果事务已经提交,则无法回滚到任何保存点。
如果尝试回滚到一个不存在的保存点,则会导致错误。
回滚到保存点时,指定的保存点及其之后的操作都将被撤销。也就是说,如果回滚到某个保存点,那么该保存点以后的操作都将失效,并且不能再次提交。
因此,在使用 ROLLBACK TO SAVEPOINT 时,必须小心谨慎,确保正确地控制事务的执行和回滚。同时,在设计数据库事务时,也应该充分考虑到使用 SAVEPOINT 和 ROLLBACK TO SAVEPOINT 的情况,以便更好地管理数据的一致性和完整性。
1.3事务的四大特征!(ACID)
- 原子性(Atomicity):事务的所有操作要么全部完成,要么全部回滚,不会出现只执行了部分操作的情况。
- 一致性(Consistency):事务执行前后数据库的状态必须保持一致,即从一个一致性状态到另一个一致性状态。
- 隔离性(Isolation):多个事务并发执行时,每个事务都应该感觉不到其他事务的存在。
- 持久性(Durability):一旦事务提交,其所做的修改就应该永久保存在数据库中,即使出现系统故障或崩溃的情况也不应该影响数据的持久性。
1.4mysql并发事务出现的问题
多个并发事务可能会同时修改同一个数据集,从而导致一些问题。
脏读(Dirty Read) - 当一个事务正在向数据库写入数据时,另一个事务读取到了这个未提交的数据,导致读取到了不正确的数据。
不可重复读(Non-Repeatable Read) - 在一个事务中,多次读取同一行记录,但在事务执行期间,其他事务已修改或删除了该行记录,导致不同的结果。
幻读(Phantom Read) - 在一个事务中多次查询某个范围内的记录,但在事务执行期间,其他事务已经插入或删除了该范围内的记录,导致结果集合不一致。
更新丢失(Lost Update) - 当两个或更多的事务尝试同时更新同一行记录时,其中一个事务的更新可能会被覆盖或丢失。
1.脏读
A事务读取B事务尚未提交的数据,此时如果B事务发生错误并执行回滚操作,那么A事务读取到的数据就是脏数据。就好像原本的数据比较干净、纯粹,此时由于B事务更改了它,这个数据变得不再纯粹。这个时候A事务立即读取了这个脏数据,但事务B良心发现,又用回滚把数据恢复成原来干净、纯粹的样子,而事务A却什么都不知道,最终结果就是事务A读取了此次的脏数据,称为脏读
2.不可重复读(前后多次读取,数据内容不一致)
事务A在执行读取操作,由整个事务A比较大,前后读取同一条数据需要经历很长的时间 。而在事务A第一次读取数据,比如此时读取了小明的年龄为20岁,事务B执行更改操作,将小明的年龄更改为30岁,此时事务A第二次读取到小明的年龄时,发现其年龄是30岁,和之前的数据不一样了,也就是数据不重复了,系统不可以读取到重复的数据,成为不可重复读。
同一个事务A读取到一条数据,但是同一条数据不一致,(事务b修改了)。
幻读(前后多次读取,数据总量不一致)
事务A在执行读取操作,需要两次统计数据的总量,前一次查询数据总量后,此时事务B执行了新增数据的操作并提交后,这个时候事务A读取的数据总量和之前统计的不一样,就像产生了幻觉一样,平白无故的多了几条数据,成为幻读。
幻读两种说法:
说法一:事务 A 根据条件查询得到了 N 条数据,但此时事务 B 删除或者增加了 M 条符合事务 A 查询条件的数据,这样当事务 A 再次进行查询的时候真实的数据集已经发生了变化,但是A却查询不出来这种变化,因此产生了幻读。
这一种说法强调幻读在于某一个范围内的数据行变多或者是变少了,侧重说明的是数据集不一样导致了产生了幻读。
说法二:幻读并不是说两次读取获取的结果集不同,幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作。更为具体一些:A事务select 某记录是否存在,结果为不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。产生这样的原因是因为有另一个事务往表中插入了数据。
幻读
说的是存不存在的问题:原来不存在的,现在存在了,则是幻读不可重复读
说的是变没变化的问题:原来是A,现在却变为了B,则为不可重复读
1.5事务的隔离级别
读未提交(Read uncommitted),
读已提交(Read committed),
可重复读(Repeatable read),
可串行化(Serializable)
1.读未提交(Read uncommitted)
在这种隔离级别下,所有事务能够读取其他事务未提交的数据。读取其他事务未提交的数据,会造成脏读。因此在该种隔离级别下,不能解决脏读、不可重复读和幻读。
2.读已提交(Read committed)
在这种隔离级别下,所有事务只能读取其他事务已经提交的内容。能够彻底解决脏读的现象。但在这种隔离级别下,会出现一个事务的前后多次的查询中却返回了不同内容的数据的现象,也就是出现了不可重复读。
注意【1】这是大多数数据库系统默认的隔离级别,例如Oracle和SQL Server,但mysql不是。
可重复读(Repeatable read)
在这种隔离级别下,所有事务前后多次的读取到的数据内容是不变的。也就是某个事务在执行的过程中,不允许其他事务进行update操作,但允许其他事务进行add操作,造成某个事务前后多次读取到的数据总量不一致的现象,从而产生幻读。
mysql的默认事务隔离级别
四、可串行化(serializable)
在这种隔离级别下,所有的事务顺序执行,所以他们之间不存在冲突,从而能有效地解决脏读、不可重复读和幻读的现象。
但是安全和效率不能兼得,这样事务隔离级别,会导致大量的操作超时和锁竞争,从而大大降低数据库的性能,一般不使用这样事务隔离级别。
事务隔离级别演示:
注意:
1.建表的时候,选择 Innodb引擎才支持事务
2.默认情况下, MySQL 是自动提交事务, 每次执行一个 SQL 语句时, 如果执行成功, 就会向数据库自动提交,而不能回滚。 如果某一组操作需要在一个事务中, 那么需要使用 start transaction(或 begin), 一旦 rollback 或 commit 就结束当次事务, 之后的操作又自动提交。
3.如果需要在当前会话的整个过程中都取消自动提交事务, 进行手动提交事务, 就需要设置 set autocommit = false(或 set autocommit = 0),那样的话每一句 SQL 都需要手动 commit 提交才会真正生效。 rollback 或 commit 之前的所有操作都视为一个事务, 之后的操作视为另一个事务, 还需要手动提交或回滚。
4.和 Oracle 一样, DDL (数据定义语言)语句是不能回滚的, 并且部分的 DDL 语句会造成隐式的提交, 因此最好事务中不要涉及DDL 语句。
语句:
开启一个事务:start transaction (begin)
提交事务:commit
回滚事务:rollback
查看全局事务隔离级别:SELECT @@global.tx_isolation;
设置全局事务隔离级别:set global transaction isolation level read committed(set global tx_isolation =‘read-committed’)
查看当前会话事务隔离级别:SELECT @@tx_isolation(show variables like ‘tx_isolation’)
设置当前会话事务隔离级别:set session transaction isolation level read committed(set tx_isolation =‘repeatable-read’)
查看mysql默认自动提交状态:select @@autocommit(show variables like ‘autocommit’)
设置mysql默认自动提交状态:set autocommit = 0(set autocommit = false;【不自动提交】)
演示:
开启两个客户端进行演示
(查看全局事务隔离级别)
(设置全局的隔离事务级别----全局会进行修改)
(会话1,设置当前的隔离模式为ru,会话2为默认rr)
(全部设置成 ru 读未提交)
(查看mysql默认提交状态,并设置成手动提交)
上边只是简单的设置操作,下边展示隔离事务的操作
1.演示脏读 (一个事务可以读取另一个事务未提交的数据)(RU)
开启两个客户端,进行设置ru隔离级别(以上操作即可)
用my_tablle数据库
脏读演示
这时会话2,还没有进行手动提交或回滚数据,也会出现不可重复读!
2.(RC,读已提交演示)
设置当前会话为 rc
这个时候,就读不到会话一还未提交的数据!!
rc的隔离级别解决了脏读问题,但是没有解决不可重复读;
3.(RR,可重复读)mysql默认隔离级别
会话1已经提交,会话2查询了两遍,两条数据,避免了不可重复读。
展示幻读现象
会话1,第一次没有读到数据 ,会话2直接插入数据并提交,会话1插入提交,到最后得到了两条数据。
可串行化自己试试吧!