戴正平 發表於 2026-1-8 09:42:46

MySQL的事务和视图使用及说明

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">1. 什么是事务</a></li><li><a href="#_label1">2. 事务的四个特征</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_0">2.1 原子性</a></li><li><a href="#_lab2_1_1">2.2 一致性</a></li><li><a href="#_lab2_1_2">2.3 持久性</a></li><li><a href="#_lab2_1_3">2.4 隔离性</a></li><ul class="third_class_ul"><li><a href="#_label3_1_3_0">2.4.1 脏读问题</a></li><li><a href="#_label3_1_3_1">2.4.2 不可重复读问题</a></li><li><a href="#_label3_1_3_2">2.4.3 幻读问题</a></li></ul><li><a href="#_lab2_1_4">2.5 事务的使用</a></li><ul class="third_class_ul"><li><a href="#_label3_1_4_3">2.5.1创建事务</a></li><li><a href="#_label3_1_4_4">2.5.2 保存点</a></li></ul><li><a href="#_lab2_1_5">2.6数据库的隔离级别</a></li><ul class="third_class_ul"><li><a href="#_label3_1_5_5">2.6.1 修改隔离级别</a></li><li><a href="#_label3_1_5_6">2.6.2 查看隔离级别</a></li><li><a href="#_label3_1_5_7">2.5.3读未提交(read uncommitted)</a></li><li><a href="#_label3_1_5_8">2.5.4读已提交(read committed)</a></li><li><a href="#_label3_1_5_9">2.5.5可重复读(repeatable read)</a></li><li><a href="#_label3_1_5_10">2.5.6串行化(serializable)</a></li></ul></ul><li><a href="#_label2">3. 视图</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_6">3.1 什么是视图</a></li><ul class="third_class_ul"></ul><li><a href="#_lab2_2_7">3.2 创建视图</a></li><ul class="third_class_ul"></ul><li><a href="#_lab2_2_8">3.3 修改视图</a></li><ul class="third_class_ul"><li><a href="#_label3_2_8_11">3.3.1 修改表</a></li><li><a href="#_label3_2_8_12">3.3.2 修改视图</a></li><li><a href="#_label3_2_8_13">3.3.3 视图不可修改</a></li></ul><li><a href="#_lab2_2_9">3.4 删除视图</a></li><ul class="third_class_ul"></ul></ul><li><a href="#_label3">总结</a></li><ul class="second_class_ul"></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>1. 什么是事务</h2>
<p>事务就是把多条SQL语句打包成一个整体,里面的sql语句要么全部执行成功,要么都不执行,这里的都不执行,不是说真没执行,而是发生了回滚操作。</p>
<p>比如说有两个人A和B,A有 1000, B有1000,</p>
<ul><li>语句一:A给B转账500,</li><li>语句二:B接收转过来的500。</li></ul>
<p>这两个sql语句需要都执行,不能中断,要成功都成功。</p>
<p>如果执行完语句一后,没有执行语句二就会出现回滚现象,执行回滚的SQL语句,返回到语句一执行前的状态。</p>
<p><strong>回滚:</strong></p>
<p>就是事务中的SQL语句执行一部分,后面没执行,此时MySQL会执行回滚的sql语句将数据恢复到该事务执行之前的状态。</p>
<p><strong>如果执行事务执行一般断电了,还会发生回滚吗?</strong></p>
<p>当然会,因为MySQL在执行事务的时候,会记录一个日志,来记录当前事务执行到那步了,当恢复电后再进入MySQL,MySQL会根据日志对之前执行的操作进行回滚,回滚完成后删除该日志,如果事务成功执行完也会删除该日志。</p>
<p><strong>引入事务可以很好的解决原子性问题。</strong></p>
<p class="maodian"><a name="_label1"></a></p><h2>2. 事务的四个特征</h2>
<p class="maodian"><a name="_lab2_1_0"></a></p><h3>2.1 原子性</h3>
<p>原子性就是说事务在执行SQL语句时候,要么全部执行,要么都不执行,不存在部分执行,部分没有执行的情况,如果存在会触发回滚操作。</p>
<p class="maodian"><a name="_lab2_1_1"></a></p><h3>2.2 一致性</h3>
<p>一致性是说事务在执行前和执行后的数据都要符合该业务的规则和约束条件。</p>
<p>比如说:银行转账业务中,余额在执行完事务后不能是负数等。</p>
<p>事务在执行前是符合一致性的,在执行一部分后中断,发生回滚回到事务执行开始,也是符合一致性的。</p>
<p>有时候,事务回滚到中间某个SQL语句,此时也有可能遵循一致性。</p>
<p class="maodian"><a name="_lab2_1_2"></a></p><h3>2.3 持久性</h3>
<p>持久性是说事务对数据做出的修改,应该持久的生效,那么就应该写入到硬盘中,加上数据库本身存储数据也是存储在硬盘上的。</p>
<p>硬盘也有坏的风险,我们需要对关键数据进行备份。</p>
<p class="maodian"><a name="_lab2_1_3"></a></p><h3>2.4 隔离性</h3>
<p>隔离性主要是谈到事务在并发情况下出现的问题:</p>
<p class="maodian"><a name="_label3_1_3_0"></a></p><h4>2.4.1 脏读问题</h4>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010809371356.png" /></p>
<p>事务1 对某个数据进行了修改,但是该事务还没结束,如果事务1发生回滚,事务2 对该数据进行访问,访问的是修改后的数据,但实际数据是没有修改的,此时读到的这个数据就是脏数据。</p>
<p><strong>如何解决脏读问题:</strong></p>
<ul><li>我们需要约束事务1执行完之后,才允许事务2读取到修改后的值。</li><li>此时如果事务1没有执行完,事务2读到的数据就是修改前的数据。</li></ul>
<p class="maodian"><a name="_label3_1_3_1"></a></p><h4>2.4.2 不可重复读问题</h4>
<p>事务1中多次读取某个值的时候,每次读取同一个数据的值都不一样,就是不可重复读。</p>
<p>同一个事务多次读取一个数据,得到的结果相同就是可重复读。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010809371329.png" /></p>
<p>图中的事务1就存在不可重复读问题。</p>
<p><strong>如何解决不可重复读问题:</strong></p>
<p>我们需要约束,一个事务在进行读操作的时候,对该数据进行加锁,其他事务不能对该数据进行读和修改的操作。当这个事务对该数据执行完后,开锁。</p>
<p>而SQL内部处理原理是:事务在执行前会创建一个&ldquo;<strong>一致性读视图</strong>&rdquo;(快照)(就是事务启动前的数据状态),该事务内部都会参照这个图去执行语句,不受外部其他事务对数据操作的影响。</p>
<p class="maodian"><a name="_label3_1_3_2"></a></p><h4>2.4.3 幻读问题</h4>
<p>同一个事务中,在针对某次查询时候,前后两个查询结果不同。</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010809371381.png" /></p>
<p>在执行事务1中第一次查询时,有3条数据,再执行事务2,在执行事务1的第二条查询,有4条数据,这就是幻读问题。</p>
<p><strong>如何解决幻读问题:</strong></p>
<p>我们就需要服务器进行串行化处理数据,也就是一个事务一个事务执行,可以解决幻读问题。</p>
<p class="maodian"><a name="_lab2_1_4"></a></p><h3>2.5 事务的使用</h3>
<p class="maodian"><a name="_label3_1_4_3"></a></p><h4>2.5.1创建事务</h4>
<div class="jb51code"><pre class="brush:sql;">#开始一个事务
start transaction;
或者
begin;

#结束一个事务
commit;

#回滚当前事务
bollback;
</pre></div>
<p class="maodian"><a name="_label3_1_4_4"></a></p><h4>2.5.2 保存点</h4>
<p>保存点是我们在创建事务的时候设置的,可以在回滚的时候,指定保存点进行回滚。使用</p>
<p>bollback to 保存点名字,回滚到保存点。</p>
<p>例如:</p>
<div class="jb51code"><pre class="brush:sql;">create table student(id int primary key, name varchar(20), balance double);
insert into student values(1,'张三',1000), (2,'李四',1500);

#开始事务
begin;

select * from student;

update student set balance = balance + 100 where name = '张三';
update student set balance = balance - 100 where name = '李四';

select * from student;

#设置保存点
savepoint tmp1;

update student set balance = balance + 100 where name = '张三';
update student set balance = balance - 100 where name = '李四';

select * from student;

#回滚到保存点
rollback to tmp1;

select * from student;

#结束事务
commit;
</pre></div>
<p class="maodian"><a name="_lab2_1_5"></a></p><h3>2.6数据库的隔离级别</h3>
<p>MySQL中规定了四种隔离级别:</p>
<ul><li>read uncommitted,读未提交,此时包含脏读,不可重复读,幻读问题。</li><li>read committed,读已提交,此时包含不可重复读,幻读问题。</li><li>repeatable read,可重复读,<strong>这种是数据库默认的级别</strong>,此时包含幻读问题。</li><li>serializable,串行化,这种就解决了所有的隔离性问题。</li></ul>
<p class="maodian"><a name="_label3_1_5_5"></a></p><h4>2.6.1 修改隔离级别</h4>
<p><strong>方法一:</strong></p>
<p>通过SQL语句进行修改:</p>
<div class="jb51code"><pre class="brush:sql;">set transaction isolation level隔离级别 | 访问模式;

#隔离级别
read uncommintted
read committed
repeatable read
serializable

#访问模式
read write #事务对数据进行读写
read only#事务对数据只进行读,不能读写。
</pre></div>
<ul><li>global:是对当前的连接以及<strong>后续连接</strong>服务器的客户端都生效。</li><li>session:只对当前连接服务器的客户端生效。</li></ul>
<p>这种使用SQL语句修改的方式只是临时修改,重启MySQL就失效了。</p>
<p><strong>方法二:</strong></p>
<p>通过配置文件修改:</p>
<p>我们可以找到自己电脑下载的MySQL所在的文件:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010809371399.png" /></p>
<p>右键文件找到属性,里面有一串地址如下:</p>
<blockquote><p>&quot;C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql.exe&quot; &quot;--defaults-file=C:\ProgramData\MySQL\MySQL Server 8.0\my.ini&quot; &quot;-uroot&quot; &quot;-p&quot; &quot;--default-character-set=utf8mb4&quot;</p></blockquote>
<p>C:\ProgramData\MySQL\MySQL Server 8.0\my.ini ,这个文件位置就是当前我电脑上的MySQL的配置文件,在里面可以修改隔离等级。</p>
<p class="maodian"><a name="_label3_1_5_6"></a></p><h4>2.6.2 查看隔离级别</h4>
<p><strong>我们还可以通过SQL语句查看数据库的隔离级别:</strong></p>
<p>查看全局会话隔离级别:</p>
<div class="jb51code"><pre class="brush:sql;">select @@global.transaction_isolation;</pre></div>
<p>查看当前会话的隔离级别:</p>
<div class="jb51code"><pre class="brush:sql;">select @@global.transaction_isolation;</pre></div>
<p class="maodian"><a name="_label3_1_5_7"></a></p><h4>2.5.3读未提交(read uncommitted)</h4>
<p>这种级别包含脏读问题,不可重复包含问题和幻读问题。</p>
<p>脏读问题:</p>
<p>我们创建第一个连接,SQL语句为,这里包含第一个事务:</p>
<div class="jb51code"><pre class="brush:sql;">create table student(id int primary key, name varchar(20), balance double);
insert into student values(1,'张三',1000), (2,'李四',1500);
select * from student;

#修改当前会话的隔离等级为读未完成
set session transaction isolation level read uncommitted;

#查询当前会话的隔离等级
select @@session.transaction_isolation;

#事务1
begin;#1

update student set balance = 2000 where id = 1;#1

commit;</pre></div>
<p>创建第二个连接,SQL语句包含第二个事务:</p>
<div class="jb51code"><pre class="brush:sql;">#修改当前会话的隔离等级为读未完成
set session transaction isolation level read uncommitted;

#查询当前会话的隔离等级
select @@session.transaction_isolation;

#事务2
begin; #2

select * from student where id = 1; #2

commit; #2</pre></div>
<p>此时两个连接的隔离级别是 read uncommitted,此时事务1的SQL语句执行前两行,事务2的SQL语句全部执行,事务2查询的结果是2000。此时产生脏读问题。当我们设置隔离等级为read committed就能解决脏读问题。</p>
<p>我们修改隔离等级为read uncommitted后,再进行上面的步骤,查询结果是1000,此时就解决了脏读问题。</p>
<p class="maodian"><a name="_label3_1_5_8"></a></p><h4>2.5.4读已提交(read committed)</h4>
<p>该隔离等级解决了脏读问题,包含不可重复读问题和幻读问题。</p>
<p>不可重复读问题:</p>
<p>第一个连接包含事务1:</p>
<div class="jb51code"><pre class="brush:sql;">#事务1
begin; #1

select * from student where id = 1; #3

select * from student where id = 1; #5

commit; #6</pre></div>
<p>第二个连接包含事务2和事务3:</p>
<div class="jb51code"><pre class="brush:sql;">#事务2
begin; #2

update student set balance = 2000 where id = 1; #2

commit; #2


#事务3
begin; #4

update student set balance = 3000 where id = 1; #4

commit; #4</pre></div>
<p>此时两个连接的隔离级别是read committed,此时按照上面注释的执行顺序,事务1中的第一次查询结果是2000,第二次查询结果是 3000,此时包含不可重复读问题。</p>
<p>我们可以修改隔离等级为repeatable read ,此时再按照上面顺序执行,事务1第一次查询结果是2000,第二次查询结果是2000,解决了不可重复读问题。</p>
<p class="maodian"><a name="_label3_1_5_9"></a></p><h4>2.5.5可重复读(repeatable read)</h4>
<p>该隔离等级解决了脏读问题,不可重复读问题,还包含着幻读问题。</p>
<p>幻读问题:</p>
<p>第一个连接包含事务1:</p>
<div class="jb51code"><pre class="brush:sql;">#事务1
begin; #1

select * from student; #1

select * from student; #3

commit; #4</pre></div>
<p>第二个连接包含事务2:</p>
<div class="jb51code"><pre class="brush:sql;">#事务2
begin; #2

insert into student values(3,'王五',2000); #2
select * from student; #2

commit; #2
</pre></div>
<p>此时的隔离等级是 repeatable read,事务1第一次查询没有王五,在执行完事务2后再次查询也没有王五。</p>
<p><strong>为什么此时没有出现幻读问题呢?</strong></p>
<ul><li>可重复读问题是在一个事务中多次读取某个数据,而该数据受到其他事务的修改而导致结果不一致(这是针对已有的数据进行修改)。</li><li>幻读问题是在一个事务中多次相同条件的查询,而结果因为其他事务的插入或者删除操作发生了修改导致不一致(这是针对新数据的插入)。</li><li>而MySQL底层解决不可重复读问题是利用一致性读视图(快照)来解决的,而其他事务的插入和删除操作不会影响快照的内容,产生的结果一致。</li></ul>
<p>下面是产生幻读问题的语句:</p>
<p>第一个连接包含事务1:</p>
<div class="jb51code"><pre class="brush:sql;">#事务1
begin; #1

select * from student; #1

insert into student values(3,'王五',2000); #3

commit; #4</pre></div>
<p>第二个连接包含事务2:</p>
<div class="jb51code"><pre class="brush:sql;">#事务2
begin; #2

insert into student values(3,'王五',2000); #2
select * from student; #2

commit; #2
</pre></div>
<p>此时先执行事务1中的查询,没有王五,再执行事务2后,此时再执行事务一的查询没有王五,但是在执行插入操作时候,会报错说明王五数据已经插入其中,因为在插入前,MySQL会查询该表是否有此元素,此时就产生幻读错误。</p>
<p>下面是事务1报错信息,这里事务2已经执行,表中已有元素事务1执行就会报错:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010809371374.png" /></p>
<p>此时我们可以修改隔离级别为serializable,此时在按照上面执行的话,就不会存在幻读问题了。此时事务之间一个一个执行。</p>
<p>下面是事务2的报错,这里事务2等到事务1执行完后才执行,此时执行事务2就会报错:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010809371395.png" /></p>
<p class="maodian"><a name="_label3_1_5_10"></a></p><h4>2.5.6串行化(serializable)</h4>
<p>该隔离级别是解决了脏读问题,不可重复读问题和幻读问题。</p>
<p><strong>隔离级别越高,执行效率越低,并发性越低,数据准确性越高。</strong></p>
<p class="maodian"><a name="_label2"></a></p><h2>3. 视图</h2>
<p class="maodian"><a name="_lab2_2_6"></a></p><h3>3.1 什么是视图</h3>
<p>视图就是一个虚假的表,不存在于硬盘上,根据其他表或者视图的查询结果作为数据生成的一张&ldquo;类似于表&rdquo;的数据集。</p>
<p class="maodian"><a name="_lab2_2_7"></a></p><h3>3.2 创建视图</h3>
<div class="jb51code"><pre class="brush:sql;">create view视图名字as查询语句;</pre></div>
<p>这里的查询语句是指从其他的表或者视图中查询的结果。</p>
<p>我们先创建一个学生表:</p>
<div class="jb51code"><pre class="brush:sql;">create table student(id int primary key, name varchar(20), sno varchar(20), age int, gender varchar(10), class_id int);
insert into student values(1,'张三','1001',20,'男',1), (2,'李四','1002',20,'男',1), (3,'王五','1003',20,'男',2),(4,'周六','1004',20,'男',2);</pre></div>
<p>创建一个视图:</p>
<div class="jb51code"><pre class="brush:sql;">create view student_class_id as select * from student where class_id = 1;
select * from student_class_id;</pre></div>
<p>该视图查询出来结果包含的就是class_id=1的学生的数据。</p>
<p>我们在创建视图的时候可以给列起指定的名字:</p>
<div class="jb51code"><pre class="brush:sql;">create view student_class_id1 (student_id,student_name) as select id,name from student where class_id = 1;
select * from student_class_id1;</pre></div>
<p>查询视图结果为:</p>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202601/2026010809371347.png" /></p>
<p class="maodian"><a name="_lab2_2_8"></a></p><h3>3.3 修改视图</h3>
<p class="maodian"><a name="_label3_2_8_11"></a></p><h4>3.3.1 修改表</h4>
<p>修改真实的表的数据,根据这个表创建的视图的数据也会被修改。</p>
<div class="jb51code"><pre class="brush:sql;">update student set age = 50 where name = '张三';
select * from student_class_id;
</pre></div>
<p>查询创建的视图,张三的年龄也被修改成50。</p>
<p class="maodian"><a name="_label3_2_8_12"></a></p><h4>3.3.2 修改视图</h4>
<p>修改视图的数据也会影响到表中的数据。</p>
<div class="jb51code"><pre class="brush:sql;">update student_class_id set age = 100 where name = '李四';
select * from student;</pre></div>
<p>查询表中李四的年龄变成了100。</p>
<p class="maodian"><a name="_label3_2_8_13"></a></p><h4>3.3.3 视图不可修改</h4>
<p>有些情况创建的视图不能够修改:</p>
<ul><li>创建视图的时候使用了聚合函数的视图。比如sum,avg等。</li><li>创建视图的时候使用了distinct,对查询结果进行了去重,此时修改不知道修改的是表中的那行数据。</li><li>创建视图的时候使用了 group by 或者 having,对查询结果进行了分组,此时视图一行数据包含表中多条数据,修改视图不知道修改表的那条数据。</li><li>创建视图的时候使用了union或者union all,合并的查询来自不同的表时候,不能确定修改视图的行对应那个表的哪个行。</li><li>创建视图的时候使用了子查询,简单的子查询可以修改视图,但是复杂的子查询,子查询的查询语句中包含聚合函数group by之类的不能修改视图。</li><li>创建视图的时候查询条件是一个表跟一个不可修改的视图进行关联查询。</li></ul>
<p class="maodian"><a name="_lab2_2_9"></a></p><h3>3.4 删除视图</h3>
<div class="jb51code"><pre class="brush:sql;">drop view student_class_id1;</pre></div>
<p class="maodian"><a name="_label3"></a></p><h2>总结</h2>
<p>以上为个人经验,希望能给大家一个参考,也希望大家多多支持琼殿技术社区。</p>
頁: [1]
查看完整版本: MySQL的事务和视图使用及说明