目录
一、数据库的三种并发场景
二、 MVCC
1. 建立共识
2. MVCC的作用
3. MVCC的三个前置知识
3.1 3个记录隐藏列字段
3.2 undo日志(undo log)
4. 模拟MVCC
4.1 图示模拟状况
4.2 不同操作的历史版本链形成
5. 当前读与快照读
6. 为什么要有隔离级别
7. read view
7.1 read view的概念
7.2 read view的简化结构
7.3 快照读与版本可见
7.4 整体流程举例
三、RR(Repeatable Read)和RC(Read Committed)之间的本质区别
1. 测试
1.1 测试1
1.2 测试2
1.3 结论
2. RR级别和RC级别可见性不同原因
四、理解MVCC
五、视图
1. 视图的概念
2. 视图的使用
3. 视图与基表的关系
4. 视图规则和限制
在以前的文章中说了,当数据库中同时存在多个事务时,就可能出现并发问题,由此需要有隔离级别。那么这些事务在同时运行时,会造成哪些并发场景呢?一共就是三个,分别为“读-读”、“读-写”和“写-写”。
在这三种并发中,存在着不同的问题。
“读-读”:不存在任何问题,也不需要并发控制。
“读-写”:有线程安全问题,可能会造成事务隔离性的问题,可能遇到脏读、幻读和不可重复读等。其实在上一篇文章“事务的隔离性”中,就是主要讲的“读-写”并发的场景。
“写-写”:有线程安全问题,可能存在更新丢失的问题,比如第一类更新丢失,第二类更新丢失。这个更新丢失的概念,在这里暂时不过多赘述。
在这里,主要着重介绍“读-写”并发。
多版本并发控制,即MVCC,是一种用来解决读-写冲突的无锁并发控制。
在了解MVCC前,要先建立几个共识。
首先,大家知道,事务在执行的过程中是原子的,但也会存在执行中这一过程。因此,mysql为了解决执行中存在的并发问题,就需要按照特定顺序执行事务。并且,在事务执行的过程中,每个事务也需要能够判断与其他事务的先后问题,以此才能判断自己是否能看到某份数据的某种状态。基于以上原因,mysql中就会为事务分配单向增长的事务id。这就说明,一个事务的id越小,就说明该事务启动的越早;id越大,就说明该事务启动的越晚。由此,就可以根据事务id的大小,来决定事务到来的先后顺序。
然后,大家应该知道,数据库中可能会面临处理多个事务的情况。这个不再赘述。同时,虽然事务在用户的眼中是原子的,但是在mysql的眼中,它是有一个从开始到结束的过程的。这就说明,事务其实也是有自己的生命周期的。
基于以上的两点,就可以得出一个结论——mysql中是需要对多个事务进行管理的,即“先描述,再组织”。这就说明, 每个事务在mysql中一定是一个或者一套对应的结构体对象/类对象,即事务也要有属于自己的结构体。由此,在mysql中对事务的管理,就可以转化为对某种数据结构的增删查改。
在上一篇文章“事务的隔离性”中写了,mysql在不同的隔离级别下,不同的事务可以看到同一份数据的不同状态。例如在可重复读(RR)级别下,正在运行的事务无法看到其他事务对同一份数据的修改。那这个功能到底是怎么实现的呢?其实就是通过MVCC机制实现的。
在了解MVCC是怎么实现不同隔离性之前,大家需要先了解三个前置知识。分别是“3个记录隐藏字段”、“undo日志”和“Read View”。
当在数据库中使用create语句建表的时候,大家可能会建1列、2列乃至更多列的表。从肉眼上来看,表中的列的个数好像和我们创建的列的个数是一样,但实际上,表中还含有3个我们看不到的隐藏列。
既然是3列,这就意味着当向表中插入数据时,这三列中的数据也会自动添加、更新。这三列数据就被称为“3个记录隐藏列字段”。
这三列的名字和内容如下:
(1)DB_TRX_ID。该列占6个字节。在上文中说了,每个事务都有一个唯一的id,而在数据库中,无论是一个sql,还是一组sql,最终都会被封装成一个事务。这些事务中就可能会对记录做修改。因此,DB_TRX_ID列就是用于保存最近修改数据的事务id。但同时,由于一条行数据中只有一个该字段,所以该字段内保存的就是最后一次对该行数据进行修改的事务id。
(2)DB_ROLL_PTR。该列占7个字节。大家应该知道,表中的数据在事务执行的过程中是可以回滚的。这个字段中保存的就是回滚指针,即执行这条记录的上一个版本。虽然在数据库中可以修改数据,但是在修改数据之前,一般数据库都会将要修改的数据备份一份并保存起来,此时这份被保存起来的备份数据就是老版本,而交由用户修改那份数据的就是最新版本。
(3)DB_ROW_ID。该列占6个字节。它是一个隐含的自增id(隐藏主键),如果数据表没有主键,innodb会自动以DB_ROW_ID产生一个聚簇索引。这一列中的内容就是在以前的文章中所说的,就算我们的表中不存在主键,mysql也会帮我们自动形成一个用于构建B+树的主键。
注意,其实在表中还有一个“删除flag隐藏列字段”。该字段用于表示某条数据是被更新了,还是被删除了。当在mysql中删除某条数据时,其实并不是真的将这份数据删除了,而是修改了删除flag,让用户看不到这条数据,显得这条数据是被删除了。当再插入数据时,就是直接覆盖被设置了删除flag的数据的位置。当然,这一条并不重要,重要的是前三个字段。
举个例子,现在有如下一张表:
从显示的结果来看,是如上所示。但是在实际上,它的形式如下所示:
首要要知道,mysql是以服务进程的方式,在内存中运行的。在之前讲的所有机制,比如索引、事务、隔离性等,都是在内存中完成的,即在mysql内部的相关缓冲区中保存相关数据,并完成各类操作。然后在合适的时候,将相关数据刷新到磁盘当中。
同时,mysql作为一个服务,它内部也是存在日志的。即在mysql中执行的各类有效操作,都需要以日志的形式保存起来,保存的位置就是undo日志。在这里,大家不用过多了解,可以将其简单的看成就是mysql中的一段内存缓冲区,用来保存日志数据的就可以了。
还有最后一个前置知识,read view。但是这个内容比较多,所以就放到后面介绍。
有了上面的知识,在这里就可以来简单模拟一下MVCC的过程了。
假设现在有如下一条数据:
现在启动一个事务10,在这个事务里面要将这条数据的name修改为“李四”。此时,mysql就会将这条数据拷贝一份,放到undo log里面。同时我们知道,undo log是一段内存缓冲区,因此,这条数据在被放入到undo log后,就会有一个地址,用于标识这条数据位置。当事务10对数据进行修改后,mysql就会将这条数据中的DB_TRX_ID修改为10,然后在DB_ROLL_PTR中填入被备份到undo log里的数据的地址:
此时的最新数据就是李四所在的行。假设现在又来一个事务11,该事务想要将该行数据中的age修改为38,此时该事务只能修改最新版本,即“李四”所在行的数据。而undo log中的数据,被称为“历史版本”,不能修改。同样的,当事务11要修改数据时,mysql会将原数据备份一份放到undo log里,然后再在最新版本的基础上做修改:
通过上面的方式,不就拥有了一条基于链表记录的历史版本链了么?因此,所谓的回滚,仅仅是从undo log中找到对应的历史版本,然后用历史版本覆盖当前的最新版本数据。当然,实际的回滚操作还是很复杂的,这里只是为了让大家理解回滚,所以就这样简单介绍。同时,这个历史版本并不是主要用来支撑回滚的,更多的是用于支撑隔离性。
此时大家可能就有一个疑惑了,这个undo log中保存了这么多的历史版本,而内存缓冲区的大小是有限的,那万一这个undo log被填满了怎么办?其实这种情况几乎是不可能发生的。
假设当前只有一个事务在运行,当这个事务对数据做修改的时候,undo log中就会保存一份又一份的备份数据。一旦这个事务commit提交了,undo log中的数据就会被全部清理掉。那如果有多个事务在同时运行呢?当有多个事务同时运行时,如果有其他事务在访问undo log中的历史版本数据,那么当一个事务提交时,不会导致这个undo log中的数据被释放掉。但一旦没有事务访问这个undo log的数据,mysql也会自动将undo log中的数据清理掉。所以对于undo log被填满的问题,大家不必过多关心。
由此,就可以解答另一个疑问了。即为什么回滚只能在一个事务运行的时候回滚,一旦事务结束,就无法回滚了。原因就是该事务保存在undo log中的历史版本在它commit的一瞬间,就已经被清理掉了。
在上面的例子当中的一个个版本,就被称为“快照”。
在上面只是更新数据,那如果是要用“delete”删除数据呢?在上文中说了,在mysql中删除数据并不是真的把数据删除,而是设置flag,表示该数据已删除。因此,删除其实也是修改对应行数据的内容,便也可以形成一个历史版本放到undo log中。
那如果是要用“insert”插入数据呢?要知道,在insert插入对应的数据之前,表中可是没有对应的行数据的。没有对应的行数据,也就意味着不是对数据做修改,即不会形成历史版本。因此,mysql中为了支持回滚操作,它会将insert后的数据也放入到undo log中。
那大家此时可能就会说了,那你将inert后的数据放进去有什么用呢?如果是要回滚到insert之前,那undo log中并没有对应的历史版本可以回滚啊。确实如此,但是在mysql中,在备份数据时,除了会将对应的数据备份进去,它还会将执行该操作的反操作记录进去。例如用insert插入了一条数据,undo log中就会记录一次delete操作。由此,如果要回滚insert的内容,mysql就会去执行delete语句。当当前事务commit后,这个undo log的历史insert记录也就会被清空了。
总的来讲,就可以理解为update和delete可以形成版本链,但insert暂时不考虑这个问题。
在这里,要先了解两个概念。读取数据时存在两种读取方式,一种是“当前读”,另一种是“快照读”。
(1)当前读:读取最新的记录,就是当前读。当执行增删改操作时,即执行insert、delete、update语句,其实都叫做当前读。同时,select也是有可能是当前读。例如“select for upadate”更新数据。
(2)快照读:读取历史版本(一般而言),就叫做快照读。
大家应该知道,无论是在RR还是在RC级别下,读-写都是可以并发的。当然,如果是写-写就可能会被阻塞住。那为什么读-写可以并发呢?其实就是因为它们读取的是不同的版本的数据。当一个事务在写的时候,势必是进行增删改操作,此时它只能对最新版本做修改。但如果是读,它读的就是历史版本,此时就不存在不同事务访问同一份数据的情况,也就不需要加锁。既然不需要加锁,当然也就可以并发执行了。
当然,如果是要读取最新版本的数据,就需要加锁,串行化运行了。但是在RR和RC级别下不存在这个问题。
那select如何知道自己应该看到哪个版本的数据呢?其实就是由隔离级别来规定的。由此就可以知道,MVCC机制在提高效率的同时,也就为隔离性提供了底层支持。
大家知道,事务都是原子的。而事务要执行,就必定要有一个先后顺序。同时一个事务中是可能存在多个sql语句的,这就注定了要将begin、CURD和commit看做一个阶段。因此,无论启动多少个事务,总会有一个先后之分。
当多个事务同时运行时,它们的CURD操作就可能交织在一起。因此,为了保证事务的“先后顺序”,就要保证这些事务只能看到它应该看到的内容,这就是隔离性和隔离界别所要解决的问题。
例如,假设在事务a执行的时候,又有一个事务b运行起来了,那事务a是否应该看到事务b对数据修改的结果呢?很明显, 不应该。这就好比你在学校里面的时候,你的学长比你先入学,当你入学的时候你的学长已经毕业找到工作了,当你去找工作时,因为学长比你先毕业,所以你可以参考学长的找工作的经验。但是学长能不能看到你找工作时的经验呢?那学长人都已经找到工作了,还看你的经验干嘛?并且你的学长就算要找工作,也是走社招,和你走到校招不是同一条路,也就更不需要你的经验了。
因此,对于事务,我们要牢记一个点:每个事务都有它应该看到的内容,也有它不应该看到的内容。按照常理来讲,先来的事务,不应该看到后来的事务对数据的修改结果。
read view也是要了解MVCC机制的前置知识之一。但由于篇幅较长,就放在这里介绍。
read view就是事务进行“快照读”操作的时候产生的“读视图(read view)”。在事务执行快照读的那一瞬间,就会生成一个数据库系统当前的快照,记录并维护系统当前活跃事务的id(注意:当每个事务开启时,都会被分配一个id,这个id是递增的,所以新的事务,它的id值越大)。
read view在mysql中,其实就是一个类,本质就是用于进行可见性判断的。在上文中讲过,在mysql中需要对事务进行管理,而事务其本身就是一个结构体对象/类对象。为了让这些事务看到不同版本的数据,便有了read view。即当某个事务执行快照读的时候,就对该记录创建一个read view读视图。可以把它看成是一个条件,用来判断当前事务能够看到哪个版本的数据。既可能是当前的最新数据,也可能是记录在undo log中的某个历史版本的数据。
为了让大家更好理解read view,这里就介绍了一下read view中的一些比较重要的字段。为了简便,这里不会全部介绍。read view的结构如下图所示:
在上图中,只需要了解圈出来的四个变量即可。
(1)m_ids。这个变量大家可以看成是一张列表,它里面记录的就是在当前事务启动的时候,mysql数据库中正在运行的事务的id。
(2)m_up_limit_id。这个变量中保存的是m_ids中最小的事务的id。它的可见性就是,当一个事务的id小于该id,那么这个事务对数据库的操作结果在当前数据库中就是可见的。
(3)m_low_limit_id。这个变量中保存的是read view生成时系统中尚未分配的下一个事务的id,也就是目前已经被使用过的事务id的最大值+1。举个例子,我们知道事务是有短事务与长事务之分的。假设当一个id为5的事务启动的时候,数据库中当前正在运行的最大事务的id为6,但是在这之前,id为7、 8、 9的事务已经跑完了。此时,数据库中系统尚未分配的下一个事务的id就为10,因此,id为5的事务的read view中的m_low_limit_id的值就应该为10,而不应该为7。
(4)m_creator_trx_id。这个变量中保存的就是创建该read view读视图的事务的id。
有了上面关于read view和数据版本链的知识后,就可以知道,在实际读取数据版本链时,是可以读取到每一个版本对应的事务id的。因为在版本中的DB_TRX_ID列里面就保存了对应的事务id。
现在就有了read view这一版本可见性的标准和DB_TRX_ID这一标识版本对应的事务id的数据。结合这两个数据,要在一个事务中判断其他版本的可见性就很简单了。就是拿着这个版本对应的事务id到该事务的read view中进行大小比对,符合条件即可见,不符合条件即不可见。
在mysql数据库中的所有事务,可以按照事务的状态和快照形成的时间线将其分为三个部分。分别是已经提交的事务,正在执行的事务和快照后新来的事务。
当想判断当前事务是否能读取undo log中的某个版本时,就要将该版本中的DB_TRX_ID的值拿到,然后将这个值与该事务的read view中的变量进行对比。由此,分为三种情况。
(1)与m_creator_trx_id对比。这个变量中存储的是当前事务的id。当DB_TRX_ID == m_creator_trx_id时,说明undo log保存的该版本就是当前事务自己修改数据后产生的备份。即,该事务应该看到该版本。
(2)与m_up_limit_id对比。这个变量中保存的是在当前事务启动的时候,正在运行的事务的最小id。当DB_TRX_ID < m_up_limit_id时,说明产生该版本的事务的id比形成该事务时正在运行的事务的最小id还小。即说明形成该版本的事务在当前事务之前就启动了,并且在历史上已经提交了。因此,这两个事务是串行执行的,不会互相干扰。即当前事务应该看到该版本。
(3)与m_low_limit_id对比。这个变量中保存的是当前事务启动时,系统中尚未分配的下一个事务的id。当DB_TRX_ID >= m_low_limit_id时,就说明形成该版本的事务是在当前事务形成快照之后才启动的。此时这两个事务就是并发执行的,因此,当前事务不应该看到这个版本。
至于id编号在m_up_limit_id和m_low_limit_id之间的事务,又分为两种情况。
(1)事务id在m_ids中。说明是在当前事务启动的时候就处于运行状态的,无论这些事务在当前事务运行的过程中是否commit,都不应该让该事务看到这些事务对应的版本。
(2)事务id不在m_ids中。说明该事务的事务id虽然比最小运行的事务id大,但是它在该事务执行快照读之前已经提交了,因此,应该让该事务看到它对应的版本。
总的来讲,如果一个事务要能够看到某个版本,则该版本的事务id需要小于m_up_limit_id,或者大于m_up_limit_id且小于m_low_limit_id且不存在于m_ids中。
因此,在mysql中判断一个事务是否能看到某个版本,就是用read view中的几个变量做几次判断即可。
注意:read view是一个判断事务可见性的类,它不是事务创建的时候就生成,而是在事务运行的过程中进行快照读的时候才会生成。
为了让大家更好的去理解事务如何通过read view中的变量来判断事务的可见性,这里就举一个例子。
假设当前有一个表,表中有如下数据:
然后现在有四个事务,id分别为1 ~ 4。其中事务2会在事务1和3运行的过程中进行快照读,事务4会在事务2快照读之前提交:
其中事务4会将name(张三)修改为name(李四)。因此,此时undo log中就会有一份原版本的备份,并形成版本链:
而事务2在进行快照读时所形成的read view,就可以看成如下所示。其中事务1和事务3在事务2快照读的时候还在执行,因此被放入m_ids中;在m_ids中的最小事务id是1,因此up_limit_id为1;同时在这个例子中,一共有4个事务,当事务2进行快照读时,数据库中最大的事务id为4,因此low_limit_id为4+1=5;至于creator_trx_id就是事务2的id,为2。
当事务2在快照读中要访问undo log中保存的记录的时候,就会拿着该行记录的DB_TRX_ID去和m_up_limit_id和m_low_limit_id进行比较,判断事务2能否看见该版本:
在此次比对中,undo log中的版本的事务id为4,小于low_limit_id,但大于up_limit_id。同时该id并不存在于m_ids中,因此,这个版本事务2可见。
为了方便看到这两个隔离级别之间的区别,在这里就做一次测试。在测试之前,大家要先了解“select * from 表名 lock in share mode;”语句。在一般情况下,用select语句查询时都是快照读,但使用这条语句后,mysql就会以加共享锁方式读取数据,即“当前读”。在下面的测试中会用到该语句。
首先,将mysql的全局隔离级别修改为可重复读,然后重新登录mysql:
然后准备如下一张user表:
然后插入如下数据:
做好准备工作后,就可以开始测试了。
首先,打开事务a,在里面更新age:
再在另一个窗口中打开事务b,在里面查看user表的数据:
可以看到,事务b中是看不到事务a的修改的,毕竟这是在可重复读的隔离级别下,符合预期。大家现在就应该知道,当事务a更新数据时,就已经形成了一份版本链。
然后再将事务a提交,再在事务b中查看一遍user表:
依然无法读取到事务a的修改,符合预期。
然后我们再在事务b中用“select * from user lock in share mode;”语句查看:
可以看到,虽然此时事务b没有commit,但是却可以查看到事务a对user表的修改了。这也就进一步证明了版本链的存在。
还是用这个表进行测试:
首先,启动事务a和事务b:
启动完成后,在事务a中查看一下user表,然后更新数据,直接commit:
在事务a执行这些操作的时候,事务b虽然启动着,但是它什么都不干。当事务a提交后,在事务b中查看一下user表的数据:
可以发现,此时事务b中竟然可以看到事务a的修改。此时大家就很疑惑了,不是说好的重复读级别下,某个事务不能看到其他事务对数据的修改么。那这个测试条件下事务a和事务b明明是同时运行的,为什么事务b可以看到事务a修改呢?
这个说法其实是有点问题的。大家要知道,read view是要在当前事务进行快照读的时候才会形成。要知道,在这个例子里面,虽然事务b和事务a同时在运行,但是事务a对数据做修改的时候,事务b没有执行任何操作,也就没有进行快照读,更不会形成read view。当事务a结束后,事务b才进行了快照读,形成了read view,在这个read view中,事务a的id就应该是小于事务b的read view中的m_up_limit_id的,也就可以被事务b查看到。
这也就证实了,read view并不是在事务创建的时候产生,而是在事务进行快照读的时候产生。
那为什么测试1中的事务b看不到事务a的操作呢?原因就是事务b在事务a修改事务之前,已经执行过一次select语句,进行了快照读了。此时就形成了read view,事务a的id别纳入该read view中的m_ids内,无法被事务b看到。
由此我们就可以知道,read view产生的时机不同,会影响事务的可见性。
通过上面的两个例子,就可以得出如下结论:
事务中快照读的结果是非常依赖于事务首次出现快照读的位置的。即某个事务中首次出现快照读的位置,决定了该事务后续快照读的结果。
通过上上面的结论,其实就很容易推导出RR级别和RC级别可见性不同的原因了。
首先,read view的生成时机不同,就会导致RC和RR级别下的快照读的结果不同。
在RR级别下,当某个事务对某条数据第一次快照读时,才会形成快照并创建read view,并将当前数据库中正在运行的其他事务的id记录到m_ids中。在这以后,只要这个事务还在运行,它所使用的就是同一个read view,看不到在快照读以后的其他事务对数据的修改。
但在RC级别下,事务每进行一次快照读,都会重新生成一个快照和read view,这就导致RC级别下的事务的read view是不断变化的。因此,当某个事务退出后,其他事务再进行快照读时所生成的read view中,这个已经结束的事务的id就存在两种情况。一种是小于up_limit_id;另一种是在up_limit_id和low_limit_id之间,但不存在与m_ids中,因此被视为可以看见。进而导致其他事务可以看到该事务在undo log中形成的历史版本。当然,这也就导致了RC级别下会出现“不可重复读”问题。
因此,RR级别和RC级别的本质区别就在于,RR级别下的事务只会在第一次快照读时形成read view;RC级别下的事务会在每次快照读时都重新形成新的read view
有了上面的知识,大家就应该知道,为什么MVCC(多版本并发控制)可以用于解决”读-写冲突“的无锁并发控制了。
首先,当某个事务在进行增删改操作时,它是对最新版本的数据做修改。而其他事务执行select操作时,看到的是历史版本的数据。它们看到的数据都不是同一个,当然也就不需要加锁了。
同理,为什么在RC级别以上,不同事务同时运行时,只要某个事务进行了快照读,它就看不到其他事务对数据的修改呢?也是因为它们读到的数据的版本不同。
视图是一个虚拟表,其内容由查询定义。在上文中介绍了read view读视图,但是这里的这个视图和读视图完全不同的两个东西。
视图中包含了一系列带有名称的列和行数据。当视图中的数据变化时,它会影响到基表,基表的数据变化时也会影响到视图。这个基表,大家可以简单理解为数据库中存储的数据。
至于使用视图后形成的东西,大家只需要将其看成一份表就行了。
使用视图的语法如下:
在这里面的视图名,就是给创建出来的视图取的名字;select语句,就是要形成的视图。
为了方便做实验,还是用在以前的文章中用过的表:
这是一份职员表。emp中保存的是员工信息。dept中保存到是部门和对应的部门编号。这份表是可以直接从网上搜索到的,这里就不再过多赘述。
假设现在要从emp表和dept表中分别拿到员工名和员工对应的部门名,按照以往的做法,就是直接按条件筛选:
执行图中的select语句,就可以拿到需要的数据了。有了这份数据后,假设是要拿到其中SMITH的信息,就可以再加上一个筛选条件:
但是这里有一个问题。假设在实际生产中,需要频繁的从表中拿取如上信息,那是不是意味着每次拿取的时候都需要对这两张表做笛卡尔积,然后慢慢筛选拿到数据。很明显,虽然不是不可以,但是却比较麻烦。此时,就可以使用“视图”。
就直接用上面的查询语句:
此时我们就创建了一张名字叫myview的视图。那如果再创建一次呢?测试一下:
可以看到,此时mysql就不让我们创建了,并且报错说,这个表已经存在了。根据这个报错来看,当前这个数据库内是不是多了一份叫myview的表呢?查看一下:
可以发现,确实多了一张myview表。然后再在linux下进入这个视图所在的目录,并查看里面的内容:
在以前的文章中讲过,大家在mysql中创建的数据库,在linux其实就是一个目录。这里不再赘述。可以发现,在对应的目录下出现了一个myview文件,这个文件就是刚刚创建的视图形成的文件。
然后再查看一下这个视图里面的内容:
可以看到,这个视图中的内容,不就是刚刚用select语句查询的结果么?通过这个测试就可以得出一个结论:所谓的创建视图,其实就是将select语句的查询结果形成一张表,以供用户使用。
当在实际中需要频繁的查看表中的某些内容时,就可以创建视图。方便后续的使用。
视图是用select语句形成的。那视图中的数据和基表中的数据,有什么关系呢?用上面形成的视图来进行测试。
将myview中的SMITH改成小写:
修改成功。然后我们再来查看一下基表中的内容:
可以发现,当视图中的内容修改时,它的结果也会影响到基表。
既然视图中修改数据会影响到基表,那在基表中修改数据会不会影响视图呢?再来测试一下。在基表中,重新将smith改回大写:
改完后,再来查看视图中的数据:
可以看到,视图中的smith也重新变回了大写。
通过上面的测试就可以知道,在mysql中,视图和基表是互相影响的。视图中的修改会影响到基表,基表中的修改也会影响到视图。
(1)视图和表一样,必须唯一命名。即不能出现同名视图或表名。
(2)创建视图数目无限制,但要考虑复杂查询创建为视图之后的性能影响。
(3)视图中不能添加索引,也不能有关联的触发器或默认值。
(4)视图可以提高安全性,其他用户只能看到它需要的数据。但必须具有足够的访问权限。
(5)order by子句可以用在视图中。但如果从该视图中检索数据的select语句中也含有order by,那么该视图中的order by将被覆盖。即此时视图中的order by子句失效。
(6)视图可以和表一起用。