繁体   English   中英

无法让我的查询在具有2M条目的MySQL数据库上更快地运行

[英]Can't get my query to run any faster on MySQL database with 2M entries

我有这个付款表,大约有200万个条目

CREATE TABLE IF NOT EXISTS `payments` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    `user_id` int(11) unsigned NOT NULL,
    `date` datetime NOT NULL,
    `valid_until` datetime NOT NULL,
     PRIMARY KEY (`id`),
     KEY `date_id` (`date`,`id`),
     KEY `user_id` (`user_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=2113820 ;

这个用户表来自CodeIgniter的ion_auth插件/库,带有约320k条目

CREATE TABLE IF NOT EXISTS `users` (
    `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
    `ip_address` varbinary(16) NOT NULL,
    `username` varchar(100) NOT NULL,
    `password` varchar(80) NOT NULL,
    `salt` varchar(40) DEFAULT NULL,
    `email` varchar(100) NOT NULL,
    `activation_code` varchar(40) DEFAULT NULL,
    `forgotten_password_code` varchar(40) DEFAULT NULL,
    `forgotten_password_time` int(11) unsigned DEFAULT NULL,
    `remember_code` varchar(40) DEFAULT NULL,
    `created_on` int(11) unsigned NOT NULL,
    `last_login` int(11) unsigned DEFAULT NULL,
    `active` tinyint(1) unsigned DEFAULT NULL,
    `first_name` varchar(50) DEFAULT NULL,
    `last_name` varchar(50) DEFAULT NULL,
    `company` varchar(100) DEFAULT NULL,
    `phone` varchar(20) DEFAULT NULL,
     PRIMARY KEY (`id`),
     KEY `name` (`first_name`,`last_name`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 AUTO_INCREMENT=322435 ;

我正在尝试同时获取用户信息和他的最后付款。 按ID,名字和姓氏,付款日期或付款到期日期排序(ASC或DESC)。 要创建一个表,以显示带有过期付款和有效付款的用户

我设法正确地获取了数据,但是在大多数情况下,我的查询对于单个用户而言花费1+秒,对于30个用户而言花费40+秒。 老实说,我不知道是否可以在1秒内获得信息。 也可能我的应用程序永远无法达到此条目数,最多可能有1万笔付款和300个用户

我的查询在输入很少的情况下效果很好,并且更改顺序很容易:

SELECT users.id, users.first_name, users.last_name, users.email, final.id AS payment_id, payment_date, final.valid_until AS payment_valid_until 
FROM users 
LEFT JOIN ( 
    SELECT * FROM ( 
        SELECT payments.id, payments.user_id, payments.date AS payment_date, payments.valid_until 
        FROM payments 
        ORDER BY payments.valid_until DESC 
        ) AS p GROUP BY p.user_id
) AS final ON final.user_id = users.id 
ORDER BY id ASC 
LIMIT 0, 30"

说明:

id  select_type         table               type              possible_keys   key       key_len   ref    rows      Extra
1   PRIMARY             users               ALL               NULL            NULL      NULL      NULL   322269    Using where; Using temporary; Using filesort
1   PRIMARY             <derived2>          ALL               NULL            NULL      NULL      NULL   50 
4   DEPENDENT SUBQUERY  users_deactivated   unique_subquery   user_id         user_id   4         func   1         Using index
2   DERIVED             <derived3>          ALL               NULL            NULL      NULL      NULL   2072327   Using temporary; Using filesort
3   DERIVED             payments            ALL               NULL            NULL      NULL      NULL   2072566   Using filesort

我乐于接受任何建议和技巧,因为我是PHP,MySQL和其他东西的新手,并且不知道我是否在使用正确的方法

我首先建议从子查询中删除ORDER BY子句-当您在外部查询中按ID重新排序时,我看不到它有什么帮助。

您还应该能够将GROUP BY语句移动到子查询中:

SELECT users.id, users.first_name, users.last_name, users.email, final.id AS payment_id, payment_date, final.valid_until AS payment_valid_until 
FROM users 
    LEFT JOIN ( 
        SELECT payments.id, payments.user_id, payments.date AS payment_date, payments.valid_until 
        FROM payments 
        GROUP BY payments.user_id
    ) AS final ON final.user_id = users.id 
ORDER BY users.id ASC 
LIMIT 0, 30

给定您的评论,如何处理-不确定是否会比您当前的查询更好,但是ORDER BY可能会很昂贵:

SELECT users.id, users.first_name, users.last_name, users.email, p.id AS payment_id, p.payment_date, p.valid_until AS payment_valid_until 
FROM users 
    LEFT JOIN payments p ON p..user_id = users.id 
    LEFT JOIN ( 
        SELECT user_id, MAX(valid_until) Max_Valid_Until
        FROM payments 
        GROUP BY user_id
    ) AS maxp ON p.user_id = maxp.user_id and p.valid_until = maxp.max_valid_until
ORDER BY users.id ASC 
LIMIT 0, 30

联接子查询的问题在于,MySql在执行联接之前会在内部生成子查询的结果。 这在资源上是昂贵的,并且可能要花费时间。 最好的解决方案是更改查询以避免子查询。

SELECT users.id, users.first_name, users.last_name, users.email, max(payments.id) AS payment_id, max(payments.date) as payment_date, max(payments.valid_until) AS payment_valid_until 
FROM users 
LEFT JOIN payments use index (user_id) on payments.user_id=users.id
group by users.id
ORDER BY id ASC 
LIMIT 0, 30

但是,如果valid_until,payment_date和payment_date的最大值始终在同一记录中,则此查询是正确的。

SELECT payments.users_id, users.first_name, users.last_name,
    users.email, (final.id), MAX(payment.date), MAX(final.valid_until) 
FROM payments final
JOIN users ON final.user_id = users.id
GROUP BY final.user_id
ORDER BY final.user_id ASC
LIMIT 0, 30

这个想法是首先将付款平摊。 MAX字段当然是不同的付款记录。


加速

上面我做了一个MySQL特有的事情:没有MAX的final.id。 最好不要使用该字段。

如果您可以省略pays.id,则速度会更快(使用适当的索引)。

 KEY `user_date` (`user_id`, `date` DESC ),
 KEY `user_valid` (`user_id`, `valid_until` DESC ),

也许是这样的...

SELECT u.id
     , u.first_name
     , u.last_name
     , u.email
     , p.id payment_id
     , p.payment_date
     , p.payment_valid_until 
  FROM users u
  JOIN payments p
    ON p.user_id = u.id
  JOIN 
     ( SELECT user_id,MAX(p.valid_until) max_valid_until FROM payments GROUP BY user_id ) x
    ON x.user_id = p.user_id
   AND x.may_valid_until = p.valid_until;

为用户在支付表上使用索引,然后在支付表上进行分组...

alter table payments add index (user_id);

您的查询

ORDER BY users.id ASC 
alter table payments drop index user_id;

为什么不使用付款“ id”代替“ valid_until”? 是否有理由不相信ID是顺序的? 如果您不相信id将索引添加到valid_until字段:

alter table payments add index (valid_until) desc;

别忘了以后放

alter table payments drop index valid_intil;

如果查询仍然很慢,则需要缓存结果...这意味着您需要改进架构,这是一个建议:

create table last_payment
(user_id int,
constraint pk_last_payment primary key user_id references users(id),
payment_id int,
constraint fk_last_payment foreign key payment_id references payments(id)
);

alter table payments add index (user_id);

insert into last_payment (user_id, payment_id)
(select user_id, max(id) from payments group by user_id);
#here you probably use your own query if the max (id) does not refer to the last payment...

alter table payments drop index user_id;

现在神奇了:

delimiter |

CREATE TRIGGER payments_trigger AFTER INSERT ON payments
  FOR EACH ROW BEGIN
    DELETE FROM last_payment WHERE user_id = NEW.user_id;
    INSERT INTO last_payment (user_id, payment_id) values (NEW.user_id, NEW.id);
  END;
|

delimiter ;

现在,每次您想知道最后一次付款时,都需要查询payments_table。

select u.*, p.* 
    from users u inner join last_payment lp on (u.id = lp.user_id)
       inner join payments on (lp.payment_id = p.id) 
    order by user_id asc;

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM