簡體   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