2

我有这张付款表,大约有 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 秒内获得信息。也可能我的应用程序永远不会达到这个数量的条目,可能最多 10k 支付和 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 和其他东西的新手,并且真的不知道我是否在做正确的方法

4

5 回答 5

1

我首先建议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
于 2013-07-14T19:20:51.217 回答
0

加入子查询的问题是,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 的最大值始终在同一记录中时,此查询才正确。

于 2013-07-14T19:29:39.837 回答
0
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。最好不要使用该字段。

如果您可以省略payments.id,它会更快(使用适当的索引)。

 KEY `user_date` (`user_id`, `date` DESC ),
 KEY `user_valid` (`user_id`, `valid_until` DESC ),
于 2013-07-14T19:37:10.010 回答
0

也许像这样的东西......

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;
于 2013-07-14T19:42:57.053 回答
0

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

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 ;

现在,每次您想知道最后一次付款时,您都需要查询 payment_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;
于 2013-07-14T19:49:36.697 回答