简体   繁体   English

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

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

I have this payments table, with about 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 ;

and this users table from ion_auth plugin/library for CodeIgniter, with about 320k entries 这个用户表来自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 ;

I'm trying to get both the user information and his last payment. 我正在尝试同时获取用户信息和他的最后付款。 Ordering(ASC or DESC) by ID, first and last name, the date of the payment, or the payment expiration date. 按ID,名字和姓氏,付款日期或付款到期日期排序(ASC或DESC)。 To create a table showing users with expired payments, and valid ones 要创建一个表,以显示带有过期付款和有效付款的用户

I've managed to get the data correctly, but most of the time, my queries take 1+ second for a single user, and 40+ seconds for 30 users. 我设法正确地获取了数据,但是在大多数情况下,我的查询对于单个用户而言花费1+秒,对于30个用户而言花费40+秒。 To be honest I have no idea if it's possible to get the information under 1 second. 老实说,我不知道是否可以在1秒内获得信息。 Also probably my application is never going to reach this number of entries, probably a maximum of 10k payments and 300 users 也可能我的应用程序永远无法达到此条目数,最多可能有1万笔付款和300个用户

My query, works pretty well with few entries and it's easy to change the ordering: 我的查询在输入很少的情况下效果很好,并且更改顺序很容易:

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"

Explain: 说明:

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

I'm open to any suggestions and tips, since I'm new to PHP, MySQL and stuff, and don't really know if I'm doing the correct way 我乐于接受任何建议和技巧,因为我是PHP,MySQL和其他东西的新手,并且不知道我是否在使用正确的方法

I would first suggest removing the ORDER BY clause from your subquery -- I don't see how it's helping as you're reordering by id in your outer query. 我首先建议从子查询中删除ORDER BY子句-当您在外部查询中按ID重新排序时,我看不到它有什么帮助。

You should also be able to move your GROUP BY statement into your subquery: 您还应该能够将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

Given your comments, how about this -- not sure it would be better than your current query, but ORDER BY can be expensive: 给定您的评论,如何处理-不确定是否会比您当前的查询更好,但是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

The problem with joining to a sub query is that MySql internally generates the result of the sub query before performing the join. 联接子查询的问题在于,MySql在执行联接之前会在内部生成子查询的结果。 This is expensive in resources and is probably taking the time. 这在资源上是昂贵的,并且可能要花费时间。 Best solution is to change the query to avoid sub queries. 最好的解决方案是更改查询以避免子查询。

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

This query is only correct , however, if the largest values for valid_until, payment_date and payment_date are always in the same record. 但是,如果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

The idea is to flatten the payments first. 这个想法是首先将付款平摊。 The MAX fields of course are of different payment records. MAX字段当然是不同的付款记录。


Speed up 加速

Above I did a MySQL specific thing: final.id without MAX. 上面我做了一个MySQL特有的事情:没有MAX的final.id。 Better not use the field at all. 最好不要使用该字段。

If you could leave out the payments.id, it would be faster (with the appropiate index). 如果您可以省略pays.id,则速度会更快(使用适当的索引)。

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

Maybe something like this... 也许是这样的...

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;

use an index on the payments table for users, that and do the group by on the payments table... 为用户在支付表上使用索引,然后在支付表上进行分组...

alter table payments add index (user_id);

your query 您的查询

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

and why don't you use the payments "id" instead of "valid_until" ? 为什么不使用付款“ id”代替“ valid_until”? Is there a reason to not trust the ids are sequential? 是否有理由不相信ID是顺序的? if you don't trust the id add index to the valid_until field: 如果您不相信id将索引添加到valid_until字段:

alter table payments add index (valid_until) desc;

and don't forget to drop it later 别忘了以后放

alter table payments drop index valid_intil;

if the query is still slow you will need to cache the results... this means you need to improve your schema, here is a suggestion: 如果查询仍然很慢,则需要缓存结果...这意味着您需要改进架构,这是一个建议:

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;

and now comes the magic: 现在神奇了:

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 ;

and now every-time you want to know the last payment made you need to query the payments_table. 现在,每次您想知道最后一次付款时,都需要查询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