我有下面的SQL查询,需要太多的时间来获取数据。
Customer.joins("LEFT OUTER JOIN renewals ON customers.id = renewals.customer_id").where("renewals.customer_id IS NULL && customers.status_id = 4").order("created_at DESC").select('first_name, last_name, customer_state, customers.created_at, customers.customer_state, customers.id, customers.status_id')
上面的查询需要230976.6ms来执行。
我添加了对firstname、lastname、customer_state和status_id的索引。
如何在不到3秒的时间内执行查询。?
1条答案
按热度按时间ezykj2lf1#
试试这个...
每个人都想要更快的数据库查询,SQL开发人员和DBA都可以使用许多经过时间考验的方法来实现这一目标。不幸的是,没有一种方法是万无一失或无懈可击的。但是,即使没有正确的答案来调优每个查询,也有很多经过验证的做和不做的事情来帮助照亮道路。虽然有些技巧是特定于RDBMS的,但大多数技巧适用于任何关系数据库。
1.使用临时表来提高游标性能
我希望我们现在都知道,如果可能的话,最好远离光标。光标不仅会受到速度问题的困扰,这本身就可能是许多操作的问题,而且还会导致您的操作阻塞其他操作的时间超过必要的时间。这大大降低了系统中的并发性。
但是,您不能总是避免使用游标,当出现这种情况时,您可以通过对临时表执行游标操作来避免游标引起的性能问题。例如,一个游标遍历一个表,并根据一些比较结果更新几个列。您可以将数据放入临时表中,然后与临时表进行比较,而不是与活动表进行比较。这样,您就有了一个针对活动表的UPDATE语句,该表要小得多,并且只在短时间内持有锁。
像这样截取数据修改可以极大地提高并发性。最后,我想说你几乎从来不需要使用光标。几乎总是有一个基于集合的解决方案;你得学着去看
1.不嵌套视图
视图可能很方便,但在使用它们时需要小心。虽然视图可以帮助隐藏用户的大型查询并标准化数据访问,但您很容易发现自己处于这样一种情况:视图调用视图,视图调用视图。这被称为嵌套视图,它可能会导致严重的性能问题,特别是在两个方面。首先,你很可能会有比你需要的更多的数据回来。其次,查询优化器将给予并返回一个错误的查询计划。
我曾经有一个客户喜欢筑巢的景色。客户端有一个视图,它几乎用于所有事情,因为它有两个重要的连接。问题是视图返回了一个包含2 MB文档的列。有些文件甚至更大。客户端在它运行的几乎每一个查询中,每一行都要通过网络推送至少2 MB的额外数据。当然,查询性能也很糟糕。
而且没有一个查询实际使用了该列!当然,这个专栏被埋在七个视图的深处,所以即使找到它也很困难。当我从视图中删除文档列时,最大查询的时间从2.5小时减少到10分钟。当我最终解开嵌套视图(其中有几个不必要的连接和列)并编写一个普通查询时,同一查询的时间下降到亚秒。
1.使用表值函数
资源视频/网络广播赞助发现您的数据困境
白皮书设计数字化工作场所时的最佳实践查看所有搜索资源Go这是我最喜欢的技巧之一,因为它确实是只有Maven才知道的隐藏秘密之一。当在查询的SELECT列表中使用标量函数时,将为结果集中的每一行调用该函数。这会显著降低大型查询的性能。但是,通过将标量函数转换为表值函数并在查询中使用CROSS APPLY,可以大大提高性能。这是一个奇妙的技巧,可以产生很大的改进。
想了解更多关于APPLY操作符的信息吗?您可以在Itzik Ben-Gan的Microsoft Virtual Academy优秀课程中找到完整的讨论。
1.使用分区以避免大数据移动
并不是每个人都能够利用这个技巧,它依赖于SQL Server Enterprise中的分区,但对于那些可以的人来说,这是一个很好的技巧。大多数人没有意识到SQL Server中的所有表都是分区的。如果愿意,可以将一个表分成多个分区,但即使是简单的表也会在创建时进行分区;但是,它们是作为单个分区创建的。如果您运行的是SQL Server Enterprise,那么您已经可以使用分区表的优势。
这意味着您可以使用分区特性(如分区)来归档仓库负载中的大量数据。让我们看一个真实的例子,来自我去年的一个客户。客户端需要将当天的表中的数据复制到归档表中;万一负载失败,公司可以用当天的表迅速恢复。由于各种原因,它不能每次都来回重命名表,所以公司每天在加载前将数据插入存档表,然后从活动表中删除当天的数据。
这个过程一开始工作得很好,但一年后,复制每个表需要1.5个小时,而且每天都要复制几个表。问题只会变得更糟。解决方案是放弃了restart和restart过程,而使用restart命令。由于将页面分配给了归档表,因此该公司可以使用该命令避免所有写入操作。只是元数据的变化。机器人平均需要两到三秒才能跑完。如果当前加载失败,则将数据重新加载到原始表中。
您可能还喜欢Microsoft Dynamics AX ERP Microsoft Dynamics AX:一个新的ERP诞生了,这一次是在云端Joseph Sirosh为什么微软的数据主管认为当前的机器学习工具就像... Urs Holzle结构谷歌的基础设施沙皇预测云业务将超过广告在5.在这种情况下,理解所有表都是分区可以减少数据加载的时间。
1.如果必须使用ORM,请使用存储过程
这是我的一个经常性的讽刺。总之,不要使用ORM(对象关系Map器)。ORM产生的代码是世界上最糟糕的代码之一,我所遇到的几乎所有性能问题都是由它们引起的。ORM代码生成器不可能像一个知道他们在做什么的人那样编写SQL。但是,如果使用ORM,请编写自己的存储过程,并让ORM调用存储过程,而不是编写自己的查询。听着,我知道所有的争论,我也知道开发人员和经理喜欢ORM,因为它们能加速你进入市场。但是当你看到查询对数据库的影响时,成本是非常高的。
存储过程有许多优点。对于初学者来说,你通过网络推送的数据要少得多。如果您有一个长查询,那么可能需要通过网络进行三到四次往返才能将整个查询发送到数据库服务器。这还不包括服务器将查询重新组合在一起并运行它所花费的时间,也不包括考虑到查询可能每秒运行几次或几百次。
使用存储过程将极大地减少通信量,因为存储过程调用总是要短得多。此外,存储过程更容易在Profiler或任何其他工具中跟踪。存储过程是数据库中的实际对象。这意味着获取存储过程的性能统计信息要比获取即席查询的性能统计信息容易得多,从而可以发现性能问题并找出异常情况。
此外,存储过程的参数设置更加一致。这意味着您更有可能重用执行计划,甚至处理缓存问题,这可能很难用临时查询来确定。存储过程还使处理边缘情况变得更加容易,甚至可以添加审计或更改锁定行为。存储过程可以处理许多会给即席查询带来麻烦的任务。几年前,我妻子解开了Entity Framework的一个两页的查询。跑了25分钟。当她将其归结为本质时,她将这个巨大的查询重写为来自T1的SELECT_SELECT(*)。别开玩笑了
好吧,我已经尽量简短了。这些是高层次的观点。我知道许多.Net程序员认为业务逻辑不属于数据库,但我能说什么,除了你完全错了。通过将业务逻辑放在应用程序的前端,您必须将所有数据通过线路传送,而仅仅是为了进行比较。这不是好的表现。今年早些时候,我有一个客户将所有逻辑都排除在数据库之外,并在前端完成所有工作。该公司将数十万行数据发送到前端,因此它可以应用业务逻辑并呈现所需的数据。我花了40分钟才做到。我把一个存储过程放在后端,让它从前端调用;页面在3秒内加载完毕。
当然,事实是,有时逻辑属于前端,有时它属于数据库。但ORM总是让我咆哮。
1.不要在同一批中的多个表上执行大型操作
这一点似乎很明显,但显然不是。我将使用另一个活生生的例子,因为它将更好地说明这一点。我有一个系统,遭受吨的阻塞。数十项行动处于停顿状态。事实证明,一个每天运行多次的删除例程在一个显式事务中删除了14个表中的数据。在一个事务中处理所有14个表意味着在所有删除操作完成之前,每个表上的锁都被保留。解决方案是将每个表的删除分解到单独的事务中,这样每个删除事务只在一个表上持有锁。这释放了其他表,减少了阻塞,并允许其他操作继续工作。您总是希望将这样的大型事务拆分为单独的较小事务,以防止阻塞。
1.不要使用触发器
这一条与前一条大体相同,但值得一提。不要使用触发器,除非它是不可避免的-它几乎总是可以避免的。
触发器的问题:无论你想让他们做什么,都将在与原始操作相同的事务中完成。如果编写触发器在更新Orders表中的一行时将数据插入另一个表,则在触发器完成之前,两个表上的锁都将保持。如果需要在更新后将数据插入到另一个表中,则将更新和插入放入存储过程中,并在单独的事务中执行。如果您需要回滚,您可以很容易地做到这一点,而不必在两个表上都持有锁。与往常一样,尽可能缩短事务,如果可以的话,不要同时锁定多个资源。
1.不要聚集在一起
这么多年了,我不敢相信我们还在为这个问题而战。但我每年至少会遇到两次集群GUID。
全局唯一标识符(globally unique identifier)是一个16字节的随机生成的数字。在此列上对表的数据进行排序将使表碎片化的速度比使用稳步增加的值(如DATE或IDENTITY)快得多。几年前,我做了一个基准测试,在测试中,我将一堆数据插入到一个具有集群的表中,并插入到另一个具有IDENTITY列的表中。可拆卸的碎片如此严重,以至于性能在短短15分钟内下降了数千%。IDENTITY表在五个小时后性能只下降了几个百分点。这不仅适用于GUID--它适用于任何volatile列。
1.如果只需要查看数据是否存在,则不要计算所有行
这是一个常见的情况。您需要查看数据是否存在于表或客户中,并根据检查结果执行一些操作。我不能告诉你我经常看到有人从dbo.T1中执行SELECT命令()来检查该数据的存在:
SET @CT =(SELECT_DATA()FROM dbo.T1);如果@CT > 0开始结束
完全没必要。如果你想检查是否存在,那么这样做:
如果存在(SELECT 1 FROM dbo.T1)开始结束
不要把所有的东西都算在table上。回到你找到的第一排。SQL Server足够聪明,可以正确使用EXISTS,并且第二个代码块返回速度超快。table越大,差异就越大。在您的数据变得太大之前,现在就做明智的事情。优化数据库永远不会太早。
实际上,我刚刚在我的一个生产数据库上对一个具有2.7亿行的表运行了这个示例。第一个查询花费了15秒,包括456,197个逻辑读取,而第二个查询在不到一秒的时间内返回,只包括五个逻辑读取。但是,如果您确实需要表中的行计数,并且它确实很大,则另一种技术是从系统表中提取它。从sysindexes中选择行将获得所有索引的行计数。因为聚集索引表示数据本身,所以可以通过添加WHERE indid = 1来获取表行。然后简单地包括表名,你就很好了。因此,最后一个查询是从sysindexes中选择行,其中object_name(id)= 'T1'且indexid = 1。在我的2.7亿行表中,这返回了亚秒级,并且只有六次逻辑读取。这才是表演
1.不要做负面搜索
以简单的查询SELECT * FROM Customers WHERE RegionID <> 3为例。您不能在此查询中使用索引,因为它是一个负搜索,必须通过表扫描逐行比较。如果您需要做类似的事情,您可能会发现,如果重写查询以使用索引,它的性能会好得多。这个查询可以很容易地重写如下:
SELECT * FROM Customers WHERE RegionID < 3 UNION ALL SELECT * FROM Customers WHERE RegionID
这个查询将使用索引,所以如果你的数据集很大,它的性能会大大超过表扫描版本。当然,没有什么事情是那么容易的,对吧?它的性能也可能更差,所以在实现它之前先测试一下。有太多的因素涉及到我告诉你,它将工作的100%的时间。最后,我意识到这个查询打破了上一篇文章中的“不要双重倾斜”提示,但这表明没有硬性规则。虽然我们在这里是双浸,我们这样做是为了避免昂贵的表扫描。
参考号:http://www.infoworld.com/article/2604472/database/10-more-dos-and-donts-for-faster-sql-queries.html
http://www.infoworld.com/article/2628420/database/database-7-performance-tips-for-faster-sql-queries.html