草庐IT

mysql - 过滤按列分组的大型数据库记录

coder 2023-10-24 原文

交易历史表:

    CREATE TABLE `TransactionHistory` (
      `id` varchar(200) NOT NULL,
      `transactionType` varchar(200) DEFAULT NULL,
      `startDate` bigint(20) DEFAULT NULL,
      `completionDate` bigint(20) DEFAULT NULL,
      `userId` varchar(200) DEFAULT NULL,
      `status` varchar(200) DEFAULT NULL,
      `error_code` varchar(200) DEFAULT NULL,
      `transactioNumber` varchar(200) DEFAULT NULL,
      PRIMARY KEY (`id`),
      KEY `transactioNumber_index` (`transactioNumber`)
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;

用户表:

    CREATE TABLE `User` (
      `userId` varchar(200) NOT NULL,
      `name` varchar(200) DEFAULT NULL,
      PRIMARY KEY (`userId`),
      KEY `userId_index` (`userId`)
    ) ENGINE=InnoDB DEFAULT CHARSET=latin1;

场景:

  • 按 transactioNumber 对 TransactionHistory 进行分组
    • 如果 groupSize == 1,
      • 在transactionType、startDate、completionDate、status、error_code中显示值
    • 如果 groupSize > 1
      • 为交易类型显示“”
      • 显示 MIN 开始日期和 MAX 开始日期
      • STATUS 和 ERROR_CODE
        • 显示状态 = SUCCESS,如果组中的所有状态 = SUCCESS,error_code = '0',
        • 如果组中的所有状态都为 FAILED,则显示状态 = FAILED,error_code = '99',
        • 显示 status = WARNING, error_code = '-1' if mixed
    • userName 的显示名称(如果交易有 userId)

我想到了这个查询:

    SELECT tx.id, 
        CASE WHEN COUNT(*) = 1 THEN transactionType ELSE '' END as transactionType,
        CASE WHEN COUNT(*) = 1 THEN status ELSE ( 
            CASE WHEN COUNT(CASE WHEN STATUS = 'SUCCESS' THEN 1 END) = 0 THEN 'FAILED' 
            WHEN COUNT(CASE WHEN STATUS = 'FAILED' THEN 1 END) = 0 THEN 'SUCCESS' 
            ELSE 'WARNING' END) END as status,
        CASE WHEN COUNT(*) = 1 THEN error_code ELSE ( 
            CASE WHEN COUNT(CASE WHEN STATUS = 'SUCCESS' THEN 1 END) = 0 THEN '99' 
            WHEN COUNT(CASE WHEN STATUS = 'FAILED' THEN 1 END) = 0 THEN '0' 
            ELSE '-1' END) END as status
        MAX(completionDate) as completionDate, 
        MIN(startDate) as startDate,
        a.userId, a.name,
        transactioNumber
    FROM TransactionHistory tx LEFT JOIN User a ON tx.userId = a.userId 
    GROUP BY transactioNumber
    LIMIT 0, 20 //pagination

但是,如果我需要添加过滤,则查询需要很长时间才能完成。我读到将 WHERE 过滤器放在 GROUP BY 之前而不是 HAVING 会更快,但我无法正确过滤状态和错误代码,因为 WARNING 和 -1 值仅在 GROUP BY 之后出现

    HAVING STATUS = 'WARNING'

此外,如果我需要计算分组条目的总数,则需要的时间太长。

我的解释显示如下

    select_type: SIMPLE
    table: tx
    type: ALL
    possible_keys: NULL
    key_len: NULL
    ref: NULL
    rows: 1140654
    Extra: Using temporary; Using filesort

    select_type: SIMPLE
    table: e
    type: eq_ref
    possible_keys: PRIMARY,id_index
    key_len: 202
    ref: db.tx.userId
    rows: 1
    Extra: Using where   

最佳答案

COUNT(CASE WHEN STATUS = 'SUCCESS' THEN 1 END)

可以缩短为

SUM(STATUS = 'SUCCESS')

这些必须按这个顺序写,它们会按这个顺序执行:WHERE, GROUP BY, HAVING。您正确地观察到您的 HAVING 不能变成 WHERE

Also if I need to count the total number of grouped entries, it takes too long.

我不明白你的意思 -- 你多次使用 COUNT(*)

transactioNumber 是否与 id 是 1:1 的关系?如果不是,则 GROUP BY 无效。

您没有 ORDER BY,因此(从技术上讲)LIMIT 定义不正确。

运行 EXPLAIN SELECT ... 以查看优化器如何执行查询。

这里有一种技术可能会有所帮助 - 通过延迟 JOIN。首先,从查询中删除所有提及 User 的内容。然后,使 SELECT 中的子查询:

SELECT z.id,
       z.transactionType,
       ...
       a.userId, a.name,
       z.transactioNumber
FROM ( SELECT id, 
              IF(COUNT(*) = 1, transactionType, '') as transactionType,
              ...
           FROM TransactionHistory
           GROUP BY transactioNumber
           ORDER BY transactioNumber
           LIMIT 0, 20
     ) z
LEFT JOIN User a ON z.userId = a.userId 

那样的话,JOIN 只会出现 20 次,而不是在 TransactionHistory 中每行出现一次。

编辑

如果没有 WHERE 子句,优化器将寻找有助于 GROUP BY 的索引。如果 ORDER BYGROUP BY 相同,则它可以同时执行 GROUP BYORDER BY同时。如果它们不同,则 ORDER BY 成为一个单独的排序步骤。

具有混合方向(例如 startdate DESC、transactionType ASC)的 ORDER BY 永远不能使用索引。注定需要一个tmp表和排序。使用 startdate DESC、transactionType DESC(均为 DESC)可能会工作得更好,而不会过多改变语义。

如果优化器不能为 GROUP BYORDER BY 使用索引,那么它必须收集所有 行并在应用 LIMIT 之前对它们进行排序。

对于 1140654 行,您想努力让查询和 INDEX 让优化器一直通过 ORDER BY 进行查询 - 这样它只需要查看 20 行,而不是 1140654。My pagination blog进入其中一些。

EXPLAIN 可能会说“使用临时文件,使用文件排序”。这可能是针对 GROUP BY 和/或 ORDER BY。但是,这隐藏了需要两种 排序的情况,一种用于GROUP BY,一种用于ORDER BYEXPLAIN FORMAT=JSON 确实明确说明了何时需要多种排序。

不过,“文件排序”并不是坏事。真正的性能 killer 是需要处理 1140654 行而不是仅仅 20 行。

关于mysql - 过滤按列分组的大型数据库记录,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31533414/

有关mysql - 过滤按列分组的大型数据库记录的更多相关文章

  1. ruby-on-rails - 按天对 Mongoid 对象进行分组 - 2

    在控制台中反复尝试之后,我想到了这种方法,可以按发生日期对类似activerecord的(Mongoid)对象进行分组。我不确定这是完成此任务的最佳方法,但它确实有效。有没有人有更好的建议,或者这是一个很好的方法?#eventsisanarrayofactiverecord-likeobjectsthatincludeatimeattributeevents.map{|event|#converteventsarrayintoanarrayofhasheswiththedayofthemonthandtheevent{:number=>event.time.day,:event=>ev

  2. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  3. ruby - Sinatra:运行 rspec 测试时记录噪音 - 2

    Sinatra新手;我正在运行一些rspec测试,但在日志中收到了一堆不需要的噪音。如何消除日志中过多的噪音?我仔细检查了环境是否设置为:test,这意味着记录器级别应设置为WARN而不是DEBUG。spec_helper:require"./app"require"sinatra"require"rspec"require"rack/test"require"database_cleaner"require"factory_girl"set:environment,:testFactoryGirl.definition_file_paths=%w{./factories./test/

  4. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  5. ruby-on-rails - Rails 5 Active Record 记录无效错误 - 2

    我有两个Rails模型,即Invoice和Invoice_details。一个Invoice_details属于Invoice,一个Invoice有多个Invoice_details。我无法使用accepts_nested_attributes_forinInvoice通过Invoice模型保存Invoice_details。我收到以下错误:(0.2ms)BEGIN(0.2ms)ROLLBACKCompleted422UnprocessableEntityin25ms(ActiveRecord:4.0ms)ActiveRecord::RecordInvalid(Validationfa

  6. ruby-on-rails - 事件管理员日期过滤器日期格式自定义 - 2

    是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s

  7. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  8. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  9. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  10. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

随机推荐