【问题标题】:Select statement can trigger dead lock on table in mysql?select语句可以触发mysql中表的死锁吗?
【发布时间】:2021-05-24 12:04:11
【问题描述】:
  • 下面的 SQL 在 MySQL 存储过程中。
  • cron 作业每天在午夜运行一次以使用结果填充报告表的过程。
  • 此过程大约需要 2 分钟才能运行。
  • 请注意,table1 拥有数百万条记录。
  • 我让它在午夜运行,因为白天有 INSERT/UPDATE 交易,但不幸的是晚上也有一些交易。
  • 当此过程运行时,如果有其他事务正在运行,则会在 table1 上发生死锁错误。

我的问题是

  1. 为什么SELECT 语句导致table1 死锁?
  2. 在这种情况下如何避免死锁?
DROP report;

CREATE TABLE IF NOT EXISTS report AS (
    
    SELECT
        DISTINCT
        
        companies.id company_id,

        (
            SELECT 
                SUM(`message_count`) single_phone
            FROM
                `table1`
            WHERE
                `table1`.`company_id` = companies.id
                AND
                `status` != 'error'
        ) AS single_phone,

        (
            SELECT 
                SUM(`message_count`)
            FROM
                `table1`
            WHERE
                `table1`.`company_id` = companies.id
                AND
                `status` != 'not error'
        ) AS log,

        (
            SELECT 
                SUM(`message_count`)
            FROM
                `table1`
            WHERE
                `table1`.`company_id` = companies.id
                AND
                `status` != 'error'
        ) AS log_monthly,

        (
            SELECT 
                SUM(`number_of_sms`) AS aggregate
            FROM
                `messages`
            WHERE
                `messages`.`company_id` = companies.id
        ) AS p_monthly
    FROM
        companies
        INNER JOIN company_users ON companies.id = company_users.company_id
    WHERE
        company_users.confirmed = 1
        AND
        company_users.deleted_at IS NULL
);

【问题讨论】:

  • 是什么让您确定这是死锁而不是任何其他类型的锁?您是否从 MySQL 服务器收到错误消息?服务器日志中有什么内容?
  • 提示:您可以通过避免在顶级 SELECT 中使用子查询来大大提高性能。相反,您可以在您 JOIN 的单个派生表中执行所有聚合。此外,您使用 SELECT DISTINCT 表明您有数据质量问题 - 像您这样的查询不应该使用 SELECT DISTINCT

标签: mysql select deadlock


【解决方案1】:

非常感谢您的帮助,但我找到了问题所在。是的,这个过程会导致表上的死锁,但问题的实际原因是我已将 ->everyMinute() 放在我的 laravel 内核中以进行计划运行。并且还有一个由另一个开发人员配置的 cron 作业,每分钟运行一次。这些将每分钟运行一次,这是死锁问题的真正原因。我已将我的内核计划更改为 ->dailyAt('02:00');现在问题解决了。

【讨论】:

    【解决方案2】:

    您的字段级查询应在 from 子句中执行一次,以便为每个公司 ID 完成一次预聚合并左连接,以防给定公司在给定类别中可能没有合格记录。此外,您获取 Single_Phone 的查询与您的“log_monthly”相同,但没有条件显示 打破或过滤活动日期以过滤出单个月与所有内容的总和。因此,我添加了一个 where 子句进行过滤,但仅在存在此类日期时才 GUESSING。

    此查询可能会大大提高您的性能。通过将每个公司 ID 的基于 COLUMN 的查询通过左连接移动到其自己的子查询中,这些查询将被 summed() 并按公司 ONCE 分组,然后是最终结果的 JOIN。使用 COALESCE(),所以如果不存在这样的计数,则返回的值将是 0 而不是 null

    DROP report;
    
    CREATE TABLE IF NOT EXISTS report AS (
          SELECT
                c.id company_id,
                coalesce( PhoneSum.Msgs, 0 ) as Single_Phone,
                coalesce( PhoneLog.Msgs, 0 ) as Log,
                coalesce( MonthLog.Msgs, 0 ) as Log_Monthly,
                coalesce( SMSSummary.Aggregate, 0 ) as p_monthly
            from
                -- this will declare an in-line variable if you do need to filter by a month as a couple of your
                -- column result names infer, but have no other indicator of filtering by a given month.
                ( select @yesterday := date_sub( date(curdate()), interval -1 day ),
                        @beginOfThatMonth := date_sub( @yesterday, interval dayOfMonth( @yesterday ) -1 day ) sqlvars,
                companies c
                    INNER JOIN company_users cu
                        ON m.company.id = cu.company_id
                        AND cu.confirmed = 1
                        AND cu.deleted_at IS NULL
        
                    LEFT JOIN
                    ( SELECT 
                            t.company_id,
                            SUM( t.message_count ) Msgs
                        FROM
                            table1 t
                                INNER JOIN company_users cu
                                    ON t.company.id = cu.company_id
                                    AND cu.confirmed = 1
                                    AND cu.deleted_at IS NULL
                        where
                            t.status != 'error'
                        GROUP BY
                            t.company_id ) AS PhoneSum,
                        on c.id = PhoneSum.company_id
        
                    LEFT JOIN
                    ( SELECT 
                            t.company_id,
                            SUM( t.message_count ) Msgs
                        FROM
                            table1 t
                                INNER JOIN company_users cu
                                    ON t.company.id = cu.company_id
                                    AND cu.confirmed = 1
                                    AND cu.deleted_at IS NULL
                        where
                            t.status != 'not error'
                        GROUP BY
                            t.company_id ) AS PhoneLog,
                        on c.id = PhoneLog.company_id
        
                    LEFT JOIN
                    ( SELECT 
                            t.company_id,
                            SUM( t.message_count ) Msgs
                        FROM
                            table1 t
                                INNER JOIN company_users cu
                                    ON t.company.id = cu.company_id
                                    AND cu.confirmed = 1
                                    AND cu.deleted_at IS NULL
                        where
                                t.status != 'error'
                            -- this would only get counts of activity for current month currently active
                            -- but since you are running at night, you need the day before current
                            AND t.SomeDateFieldOnTable1 >= @beginOfThatMonth
                        GROUP BY
                            t.company_id ) AS MonthLogMsgs,
                        on c.id = MonthLogMsgs.company_id
        
                    LEFT JOIN
                    ( SELECT 
                            m.company_id,
                            SUM( m.number_of_sms ) aggregate
                        FROM
                            messages m
                                INNER JOIN company_users cu
                                    ON m.company.id = cu.company_id
                                    AND cu.confirmed = 1
                                    AND cu.deleted_at IS NULL
                        where
                            m.SomeDateFieldOnMessagesTable >= @beginOfThatMonth
                        GROUP BY
                            company_id ) AS SMSSummary,
                        on c.id = SMSSummary.company_id
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多