一条查询语句很慢到底会引发什么后果呢?比如一个报表导出的语句需要几十秒。。。
我们通过笛卡尔积加排序创造这样一条反复全表扫描的语句:
select top 100 * from WorkLink w1,WorkLink w2,WorkLink w3
order by w1.Id asc,w2.Id asc
现在查询语句进入漫长的执行中:
这个时候,有用户执行更新操作:
update WorkLink set Title='title1' where id=4
现在又多了一条漫长执行的语句
十分钟之后
是因为id=4的这一行数据引发的吗?执行
select *from WorkLink where id=4
虽然这行数据并没有什么特别,但最起码我们发现系统的查询功能还是可以使用的。
报表导出的sql语句慢导致更新操作受到了影响。怀疑是查询语句把表锁住了,导致更新语句无法执行。将执行的语句终止,加入nolock试一下:
select top 100 * from WorkLink w1 with(nolock),WorkLink w2 with(nolock),WorkLink w3 with(nolock)
order by w1.Id asc,w2.Id asc
这时候,用户再次进行更新操作
update WorkLink set Title='title1' where id=4
成功执行,耗时忽略不计。
这条自表笛卡尔积加排序的sql语句彻底搞乱了顺序,虽然限定了取前100条,但覆盖面总会是全表,所以即使更新语句中明确指出了id=4这一行,但是整个表已经被锁住了,SqlServer数据库并没有加锁过期时间的默认设置,所以当更新语句会一直卡在尝试添加写锁的路上。
加锁是实现数据库并发控制的一个非常重要的技术。在数据库中有两种基本的锁类型:共享锁(Share Locks,即S锁)和排它锁(Exclusive Locks,即X锁)。数据库利用这两种基本的锁类型来对数据库的事务进行并发控制。
到这里我们发现,仅仅是数据库的读操作的应用场景下,数据库事物都是亟需掌握的技能,因为即使是最简单的一条查询语句,也涉及到共享锁,小到表中的某一行,大到整个表,整个库,都会受到影响。当然,最大就是到库了,这也是《跟着项目学sql——数据库事物(1)》这篇文章的用意,并没有任何贬低的意思,只是一种欲扬先抑的修辞手法,就像历史小说有王不过项,将不过李,西游强行设定老君十八化,封神造出个鸿钧,把框框交代清楚,才不会像龙珠超一样,画到最后搞得武力值崩坏。
书接上文,这条耗时很长的sql语句是精心设计出来的,
select top 100 * from WorkLink w1,WorkLink w2,WorkLink w3
order by w1.Id asc,w2.Id asc
事实上去掉后面的排序条件w2.Id asc,会瞬间取出100条 w1.id=1的数据。
select top 100 * from WorkLink w1,WorkLink w2,WorkLink w3
order by w1.Id asc
说明是排序条件的原因,导致索引失效,走了N次全表扫描。因此,过分的讨论笛卡尔积与join的效率问题在大多数情况下是没有多大意义的,比来比去,比的是内存占用的多寡,硬盘,好像还用不到硬盘。。。
就像是前文的导出全表数据功能,应用场景对上了,就是要这么写。
select * from WorkLink order by Id desc
执行取出表内将近200W条数据的sql语句,并不会造成锁表,仍然是一瞬间的事。慢的是网络,内存高以及导出excel时的代码处理了。将近200W条数据,假如有几百兆了,就是代表需要占用服务器几百兆的内存,需要网络传输几百兆的数据,第一步优化方式就是把星号去掉,按需取字段
select Id,Name from WorkLink order by Id desc
现在数据已经变成几百K了,从单纯的网络因素来看,比之前「快」了上万倍,这就是sql的神奇之处。
比如好多面试题中出现过的Oracle中sql递归语句查询,并不是无的放矢,而是经验之谈,因为人家的客户都是庞然大物,这个parentId取出来的部门可能多达几千个,考虑到网络因素,按需取字段是必须的。而相比于全部取出后用程序处理,sql递归做到了按需取数据,需要占用的内存更少,在这里,确实是上上之选。
Oracle数据库中部门表 Department中有5000个部门,如下:
Id | Name | PId | FullName | SortNum | CreateTime | ...... |
1 | 部门A | 0 | 公司一级部门A | 1 | 2019-12-20 | ...... |
2 | 部门B | 1 | 公司一级部门A下属的部门B | 61 | 2019-12-20 | |
3 | 部门C | 1 | 公司一级部门A下属的部门C | 62 | 2019-12-20 | |
4 | 部门D | 2 | 公司一级部门A下属的部门B下属部门D | 71 | 2019-12-20 | |
...... |
问题:根据PId递归取出所有相关部门,并于前端页面用树形插件展示出来(节点展示Name字段,同级部门按SortNum字段正序排列)。
数据库服务器=》web服务器=》浏览器,经过了2次网络传输。如果是面试题,我们可以跟考官询问「数据库服务器=》web服务器」是不是局域网环境,如果是的话,就可以进一步精简成服务器=》浏览器,只有这个过程一定是外网,速度会根据访问时间访问地点不停的浮动。
在这种情况下,我们只需要保证从服务器发出的数据量尽可能小就可以了,按需取字段
select Id,Name,PId from Department order by SortNum
取出后在程序中根据PId参数过滤即可。
如果并不是局域网环境,就可以考虑使用sql递归处理,按需取数据,尽可能地适应了糟糕的网络环境
select Id,Name,PId from Department start with PId=:PId connect by prior Id=PId
而且既然已经是按需取数据了,那这条sql取出的数据不需要再经过程序的任何包装处理,直接返给前端树形插件去展示就可以了。
至此,问题完美解决了,至于前端树形插件展示就不是什么值得探究的了,它的存在只是为了提醒答题者,这个问题涉及到了外部网络,需要考虑的点多一些罢了。
Html源文件
1)接收数据前:
2)接收数据并展示完成:
并没有什么明显的变化。。。。。。因为浏览器端ajax接收到的数据就是以内存的形式存在的,也就是说前端从接收到的数据到展示完成都只是在不停的操作内存,想慢都难。。给你传了500条数据,那这500条数据除非你不接,你接了,就已经占满了500条数据的内存,至于后面你到底是一次全展示还是【按需】展示还有意思吗?(一种类似客户端分页的操作)
在这个虚拟DOM横行的时代,即便是以浏览器兼容性为主的老牌树插件zTree也号称「上万节点轻松加载,即使在 IE6 下也能基本做到秒杀」
瞧,早说了重点是Sql吧!