Select 语句可以在 mysql 中触发 table 上的死锁?
Select statement can trigger dead lock on table in mysql?
- 下面的 SQL 在 MySQL 存储过程中。
- cron 作业的过程 运行 每天午夜一次,用结果填充报告 table。
- 此过程需要大约 2 分钟才能完成 运行。
- 请注意
table1
有数百万条记录。
- 我把这个放在 运行 半夜,因为白天有
INSERT
/UPDATE
笔交易,但不幸的是,晚上也有一些交易。
- 当此过程 运行s 并且如果还有其他事务 运行ning 时,则会在
table1
上发生死锁错误。
我的问题是
- 为什么
SELECT
语句会导致 table1
死锁?
- 遇到这种情况如何避免死锁?
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
);
您的字段级查询应该在 from 子句中完成一次,以便每个公司 ID 完成一次预聚合,并左连接,以防给定公司在给定类别中可能没有合格记录。此外,您获取 Single_Phone 的查询与您的 'log_monthly' 相同,但没有条件显示
打破或过滤 activity 的日期以过滤掉单个月与所有事物的总计。因此,我添加了一个用于过滤的 where 子句,但仅在存在此类日期时才猜测。
此查询可能会显着提高您的性能。通过左连接将每个公司 ID 的基于 COLUMN 的查询移动到它自己的子查询中,这些将被求和()并按公司 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
非常感谢您的帮助,但我已经找到了问题所在。是的,此过程导致 table 上的死锁,但问题的实际原因是我已将 ->everyMinute() 放入我的 laravel 内核中用于时间表 运行。并且还有另一个开发人员配置的 cron 作业,每分钟 运行 相同。这些将 运行 每分钟安排一次,这是死锁问题的真正原因。我已将内核计划更改为 ->dailyAt('02:00');现在问题解决了。
- 下面的 SQL 在 MySQL 存储过程中。
- cron 作业的过程 运行 每天午夜一次,用结果填充报告 table。
- 此过程需要大约 2 分钟才能完成 运行。
- 请注意
table1
有数百万条记录。 - 我把这个放在 运行 半夜,因为白天有
INSERT
/UPDATE
笔交易,但不幸的是,晚上也有一些交易。 - 当此过程 运行s 并且如果还有其他事务 运行ning 时,则会在
table1
上发生死锁错误。
我的问题是
- 为什么
SELECT
语句会导致table1
死锁? - 遇到这种情况如何避免死锁?
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
);
您的字段级查询应该在 from 子句中完成一次,以便每个公司 ID 完成一次预聚合,并左连接,以防给定公司在给定类别中可能没有合格记录。此外,您获取 Single_Phone 的查询与您的 'log_monthly' 相同,但没有条件显示 打破或过滤 activity 的日期以过滤掉单个月与所有事物的总计。因此,我添加了一个用于过滤的 where 子句,但仅在存在此类日期时才猜测。
此查询可能会显着提高您的性能。通过左连接将每个公司 ID 的基于 COLUMN 的查询移动到它自己的子查询中,这些将被求和()并按公司 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
非常感谢您的帮助,但我已经找到了问题所在。是的,此过程导致 table 上的死锁,但问题的实际原因是我已将 ->everyMinute() 放入我的 laravel 内核中用于时间表 运行。并且还有另一个开发人员配置的 cron 作业,每分钟 运行 相同。这些将 运行 每分钟安排一次,这是死锁问题的真正原因。我已将内核计划更改为 ->dailyAt('02:00');现在问题解决了。