আমি কীভাবে মাইএসকিউএল এর অর্ডার বাই র‌্যান্ড () ফাংশনটি অনুকূল করতে পারি?


90

আমি আমার ক্যোয়ারীগুলি অনুকূল করতে চাই যাতে আমি সন্ধান করি mysql-slow.log

আমার বেশিরভাগ ধীর প্রশ্নের মধ্যে রয়েছে ORDER BY RAND()। এই সমস্যাটি সমাধানের জন্য আমি আসল সমাধান খুঁজে পাচ্ছি না। থেরেস মাইএসকিউএলপারফরম্যান্সব্লগের একটি সম্ভাব্য সমাধান তবে এটি যথেষ্ট বলে আমি মনে করি না। দুর্বলভাবে অনুকূলিত (বা প্রায়শই আপডেট হওয়া, ব্যবহারকারীর দ্বারা পরিচালিত) টেবিলগুলিতে এটি কাজ করে না বা আমার- PHPজেনারেটেড এলোমেলো সারিটি নির্বাচন করার আগে আমাকে আরও দুটি বা আরও অনুসন্ধান চালানো দরকার ।

এই সমস্যার কোন সমাধান আছে কি?

একটি জঘন্য উদাহরণ:

SELECT  accomodation.ac_id,
        accomodation.ac_status,
        accomodation.ac_name,
        accomodation.ac_status,
        accomodation.ac_images
FROM    accomodation, accomodation_category
WHERE   accomodation.ac_status != 'draft'
        AND accomodation.ac_category = accomodation_category.acat_id
        AND accomodation_category.acat_slug != 'vendeglatohely'
        AND ac_images != 'b:0;'
ORDER BY
        RAND()
LIMIT 1

উত্তর:


67

এটা চেষ্টা কর:

SELECT  *
FROM    (
        SELECT  @cnt := COUNT(*) + 1,
                @lim := 10
        FROM    t_random
        ) vars
STRAIGHT_JOIN
        (
        SELECT  r.*,
                @lim := @lim - 1
        FROM    t_random r
        WHERE   (@cnt := @cnt - 1)
                AND RAND(20090301) < @lim / @cnt
        ) i

এটি বিশেষত দক্ষ MyISAM(যেহেতু COUNT(*)তাত্ক্ষণিক) তবে InnoDBএটি এর 10চেয়ে কয়েকগুণ বেশি দক্ষ ORDER BY RAND()

এখানে মূল ধারণাটি হ'ল আমরা বাছাই করি না, পরিবর্তে দুটি পরিবর্তনশীল রাখি এবং গণনা করি running probability রাখি এবং বর্তমান ধাপে নির্বাচিত জন্য একটি সারিটির ।

আরও বিস্তারিত জানার জন্য এই নিবন্ধটি আমার ব্লগে দেখুন:

হালনাগাদ:

যদি আপনাকে কেবল একটি একক এলোমেলো রেকর্ড নির্বাচন করতে হয় তবে এটি ব্যবহার করে দেখুন:

SELECT  aco.*
FROM    (
        SELECT  minid + FLOOR((maxid - minid) * RAND()) AS randid
        FROM    (
                SELECT  MAX(ac_id) AS maxid, MIN(ac_id) AS minid
                FROM    accomodation
                ) q
        ) q2
JOIN    accomodation aco
ON      aco.ac_id =
        COALESCE
        (
        (
        SELECT  accomodation.ac_id
        FROM    accomodation
        WHERE   ac_id > randid
                AND ac_status != 'draft'
                AND ac_images != 'b:0;'
                AND NOT EXISTS
                (
                SELECT  NULL
                FROM    accomodation_category
                WHERE   acat_id = ac_category
                        AND acat_slug = 'vendeglatohely'
                )
        ORDER BY
                ac_id
        LIMIT   1
        ),
        (
        SELECT  accomodation.ac_id
        FROM    accomodation
        WHERE   ac_status != 'draft'
                AND ac_images != 'b:0;'
                AND NOT EXISTS
                (
                SELECT  NULL
                FROM    accomodation_category
                WHERE   acat_id = ac_category
                        AND acat_slug = 'vendeglatohely'
                )
        ORDER BY
                ac_id
        LIMIT   1
        )
        )

এটি ধরে নিয়েছে আপনার ac_idগুলি কমবেশি সমানভাবে বিতরণ করা হয়েছে।


হ্যালো, কাসনসুই! সবার আগে, আপনার দ্রুত প্রতিক্রিয়ার জন্য ধন্যবাদ! হয়তো এটি আমার দোষ তবে এটি এখনও আপনার সমাধান অস্পষ্ট। আমি আমার মূল পোস্টটি একটি দৃ concrete় উদাহরণ সহ আপডেট করব এবং আপনি যদি এই উদাহরণটিতে আপনার সমাধানটি ব্যাখ্যা করেন তবে আমি খুশি হব।
ফেব্রিক

"জয়ো অ্যাডোয়েডেশন অ্যাকো ওএন aco.id =" এ টাইপো ছিল যেখানে aco.id সত্যিই aco.ac_id। অন্যদিকে সংশোধন করা কোয়েরিটি আমার পক্ষে কাজ করেনি কারণ এটি একটি ত্রুটি # 1241 ছুড়ে ফেলেছে - অপেরান্ডে পঞ্চম নির্বাচন (চতুর্থ উপ-নির্বাচন) এ 1 কলাম (গুলি) থাকা উচিত। আমি প্রথম বন্ধনী নিয়ে সমস্যাটি অনুসন্ধান করার চেষ্টা করেছি (যদি আমি ভুল না হয়) তবে আমি এখনও সমস্যাটি খুঁজে পাই না।
ফেব্রিক

@fabrik: এখন চেষ্টা কর. আপনি যদি টেবিল স্ক্রিপ্টগুলি পোস্ট করেন তবে এটি সত্যিই সহায়ক হবে যাতে আমি পোস্টের আগে তাদের চেক করতে পারি।
কাসনসুই

ধন্যবাদ, এটি কাজ করে! :) আপনি কি জয়েনকে সম্পাদনা করতে পারবেন ... aco.id অংশে যোগ দিতে ... aco.ac_id তে যাতে আমি আপনার সমাধানটি গ্রহণ করতে পারি। আবার ধন্যবাদ! একটি প্রশ্ন: আমি ভাবছি যদি এটি সম্ভব হয় তবে এটি আরও খারাপ এলোমেলোভাবে অর্ডার বাই র‌্যান্ড () এর মতো? এই প্রশ্নের কারণে বেশ কয়েকটি ফলাফল (গুলি) বারবার বলা হচ্ছে।
ফেব্রিক

4
@ অ্যাডাম: না, এটি ইচ্ছাকৃত, যাতে আপনি ফলাফলগুলি পুনরুত্পাদন করতে পারেন।
কাসনসুই

12

এটি আপনাকে কতটা এলোমেলো হতে হবে তার উপর নির্ভর করে। আপনার সংযুক্ত সমাধানটি আইএমওতে বেশ ভাল কাজ করে। আইডি ক্ষেত্রে আপনার বড় ফাঁক না থাকলে এটি এখনও বেশ এলোমেলো।

তবে এটি ব্যবহার করে আপনার একটি ক্যোয়ারিতে এটি করতে সক্ষম হওয়া উচিত (একক মান নির্বাচনের জন্য):

SELECT [fields] FROM [table] WHERE id >= FLOOR(RAND()*MAX(id)) LIMIT 1

অন্যান্য সমাধান:

  • randomটেবিলটিতে ডাকা একটি স্থায়ী ফ্লোট ফিল্ড যুক্ত করুন এবং এলোমেলো সংখ্যায় এটি পূরণ করুন। তারপরে আপনি পিএইচপিতে একটি এলোমেলো সংখ্যা তৈরি করতে পারেন এবং করতে পারেন"SELECT ... WHERE rnd > $random"
  • আইডিগুলির পুরো তালিকাটি ধরুন এবং একটি পাঠ্য ফাইলে ক্যাশে করুন। ফাইলটি পড়ুন এবং এ থেকে এলোমেলো আইডি চয়ন করুন।
  • ক্যোয়ারির ফলাফলগুলি এইচটিএমএল হিসাবে ক্যাশে করুন এবং এটি কয়েক ঘন্টা রাখুন।

8
এটি কি আমি বা এই কোয়েরিটি কাজ করে না? আমি এটি বিভিন্ন রূপ দিয়ে চেষ্টা করেছি এবং তারা সবাই "গ্রুপ ফাংশনটির অবৈধ ব্যবহার" ফেলে
দিয়েছি

আপনি এটি একটি subquery দিয়ে করতে পারেন তবে এটি SELECT [fields] FROM [table] WHERE id >= FLOOR(RAND()*(SELECT MAX(id) FROM [table])) LIMIT 1সঠিকভাবে কাজ করছে বলে মনে হচ্ছে না কারণ এটি কখনই শেষ রেকর্ড ফেরায় না
মার্ক

11
SELECT [fields] FROM [table] WHERE id >= FLOOR(1 + RAND()*(SELECT MAX(id) FROM [table])) LIMIT 1মনে হচ্ছে আমার জন্য কৌশলটি করছেন
মার্ক

1

আমি এটি কীভাবে করব তা এখানে:

SET @r := (SELECT ROUND(RAND() * (SELECT COUNT(*)
  FROM    accomodation a
  JOIN    accomodation_category c
    ON (a.ac_category = c.acat_id)
  WHERE   a.ac_status != 'draft'
        AND c.acat_slug != 'vendeglatohely'
        AND a.ac_images != 'b:0;';

SET @sql := CONCAT('
  SELECT  a.ac_id,
        a.ac_status,
        a.ac_name,
        a.ac_status,
        a.ac_images
  FROM    accomodation a
  JOIN    accomodation_category c
    ON (a.ac_category = c.acat_id)
  WHERE   a.ac_status != ''draft''
        AND c.acat_slug != ''vendeglatohely''
        AND a.ac_images != ''b:0;''
  LIMIT ', @r, ', 1');

PREPARE stmt1 FROM @sql;

EXECUTE stmt1;


আমার টেবিলটি অবিচ্ছিন্ন নয় কারণ এটি প্রায়শই সম্পাদিত হয়। উদাহরণস্বরূপ বর্তমানে প্রথম আইডিটি 121.
ফেব্রিক

4
উপরের কৌশলটি আইডি মানগুলি অবিচ্ছিন্ন হওয়ার উপর নির্ভর করে না। এটি অন্যান্য সমাধানের মতো 1 এবং MAX (আইডি) নয় 1 এবং COUNT (*) এর মধ্যে একটি এলোমেলো সংখ্যা নির্বাচন করে।
বিল কারভিন 17

4
ব্যবহার OFFSET(যা কি @rজন্য) একটি স্ক্যান এড়াতে না - আপ একটি পূর্ণাঙ্গ তালিকা স্ক্যান করতে বলুন।
রিক জেমস 21

@ রিকজেমস, এটা ঠিক। আমি যদি আজ এই প্রশ্নের উত্তর দিই, আমি প্রাথমিক কী দ্বারা কোয়েরি করতাম। LIMIT এর সাথে অফসেট ব্যবহার করা প্রচুর সারি স্ক্যান করে। প্রাথমিক কী দ্বারা জিজ্ঞাসা করা, যদিও অনেক দ্রুত, প্রতিটি সারি বেছে নেওয়ার এমনকি সুযোগের নিশ্চয়তা দেয় না - এটি ফাঁক অনুসরণকারী সারিগুলির পক্ষে ors
বিল কারভিন

1

(হ্যাঁ, আমি এখানে পর্যাপ্ত মাংস না খেয়ে ডিনারে ডুবিয়ে দেব, তবে আপনি কি একদিনের জন্য নিরামিষ হয়ে উঠতে পারবেন না?)

কেস: ফাঁক ছাড়া টানা AUTO_INCREMENT, 1 টি সারি ফিরে
ফাঁক ছাড়া টানা AUTO_INCREMENT, 10 সারি: কেস
কেস: ফাঁক দিয়ে AUTO_INCREMENT, 1 টি সারি ফিরে
randomizing জন্য অতিরিক্ত ভাসা কলাম: কেস
UUID অথবা MD5 কলাম: কেস

এই 5 টি কেস বড় টেবিলগুলির জন্য খুব দক্ষ করা যায়। বিস্তারিত জানার জন্য আমার ব্লগ দেখুন ।


0

এটি আপনাকে এমন একক সাব কোয়েরি দেবে যা সূচিটি একটি এলোমেলো আইডি পেতে ব্যবহার করবে তবে অন্য ক্যোয়ারীটি আপনার যুক্ত টেবিল পেয়ে আগুন ধরিয়ে দেবে।

SELECT  accomodation.ac_id,
        accomodation.ac_status,
        accomodation.ac_name,
        accomodation.ac_status,
        accomodation.ac_images
FROM    accomodation, accomodation_category
WHERE   accomodation.ac_status != 'draft'
        AND accomodation.ac_category = accomodation_category.acat_id
        AND accomodation_category.acat_slug != 'vendeglatohely'
        AND ac_images != 'b:0;'
AND accomodation.ac_id IS IN (
        SELECT accomodation.ac_id FROM accomodation ORDER BY RAND() LIMIT 1
)

0

আপনার ডামি-উদাহরণের সমাধানটি হ'ল:

SELECT  accomodation.ac_id,
        accomodation.ac_status,
        accomodation.ac_name,
        accomodation.ac_status,
        accomodation.ac_images
FROM    accomodation,
        JOIN 
            accomodation_category 
            ON accomodation.ac_category = accomodation_category.acat_id
        JOIN 
            ( 
               SELECT CEIL(RAND()*(SELECT MAX(ac_id) FROM accomodation)) AS ac_id
            ) AS Choices 
            USING (ac_id)
WHERE   accomodation.ac_id >= Choices.ac_id 
        AND accomodation.ac_status != 'draft'
        AND accomodation_category.acat_slug != 'vendeglatohely'
        AND ac_images != 'b:0;'
LIMIT 1

বিকল্প সম্পর্কে আরও ORDER BY RAND()পড়তে আপনার এই নিবন্ধটি পড়া উচিত ।


0

আমি আমার প্রকল্পে বিদ্যমান প্রচুর প্রশ্নগুলি অনুকূল করে তুলছি। কাসনসুইয়ের সমাধান আমাকে ক্যোয়ারীগুলিকে অনেক গতিতে সাহায্য করেছে! তবে, আমি সমস্ত প্রশ্নের মধ্যে উল্লিখিত সমাধানটি সংযুক্ত করা কঠিন বলে মনে করি, বিশেষত একাধিক বৃহত টেবিলগুলিতে অনেকগুলি সাব-কোয়েরিতে জড়িত জটিল প্রশ্নের জন্য।

সুতরাং আমি একটি কম অপ্টিমাইজড সমাধান ব্যবহার করছি। মূলত এটি কাসনসুইয়ের সমাধান হিসাবে একইভাবে কাজ করে।

SELECT  accomodation.ac_id,
        accomodation.ac_status,
        accomodation.ac_name,
        accomodation.ac_status,
        accomodation.ac_images
FROM    accomodation, accomodation_category
WHERE   accomodation.ac_status != 'draft'
        AND accomodation.ac_category = accomodation_category.acat_id
        AND accomodation_category.acat_slug != 'vendeglatohely'
        AND ac_images != 'b:0;'
        AND rand() <= $size * $factor / [accomodation_table_row_count]
LIMIT $size

$size * $factor / [accomodation_table_row_count]এলোমেলো সারি বাছাইয়ের সম্ভাবনাটি কাজ করে। র‌্যান্ড () একটি এলোমেলো সংখ্যা তৈরি করবে। সারিটি নির্বাচন করা হবে যদি র‌্যান্ড () ছোট হয় বা সম্ভাবনার সমান হয়। এটি কার্যকরভাবে সারণির আকার সীমাবদ্ধ করতে একটি এলোমেলো নির্বাচন সম্পাদন করে। যেহেতু সম্ভাবনা রয়েছে এটি নির্ধারিত সীমা গণনার চেয়ে কম ফিরে আসবে, তাই আমরা পর্যাপ্ত সারি নির্বাচন করছি তা নিশ্চিত করার জন্য আমাদের সম্ভাবনা বাড়ানো দরকার। অতএব আমরা $ আকারকে $ গুণক দ্বারা গুণ করি (আমি সাধারণত I গুণক = 2 সেট করি, বেশিরভাগ ক্ষেত্রে কাজ করে)। অবশেষে আমরাlimit $size

সমস্যা এখন আবাসন_সমাজ_সামগ্রী_কাউন্টটি কাজ করছে । যদি আমরা টেবিলের আকারটি জানি তবে আমরা টেবিলের আকারটি হার্ড কোড করতে পারি। এটি দ্রুততম চলবে, তবে অবশ্যই এটি আদর্শ নয়। আপনি যদি মাইসাম ব্যবহার করছেন তবে টেবিলের গণনা পাওয়া খুব কার্যকর। যেহেতু আমি ইনোডাব ব্যবহার করছি, আমি কেবল একটি সাধারণ গণনা + নির্বাচন করছি। আপনার ক্ষেত্রে এটি দেখতে এই রকম হবে:

SELECT  accomodation.ac_id,
        accomodation.ac_status,
        accomodation.ac_name,
        accomodation.ac_status,
        accomodation.ac_images
FROM    accomodation, accomodation_category
WHERE   accomodation.ac_status != 'draft'
        AND accomodation.ac_category = accomodation_category.acat_id
        AND accomodation_category.acat_slug != 'vendeglatohely'
        AND ac_images != 'b:0;'
        AND rand() <= $size * $factor / (select (SELECT count(*) FROM `accomodation`) * (SELECT count(*) FROM `accomodation_category`))
LIMIT $size

জটিল অংশটি সঠিক সম্ভাবনার বাইরে চলেছে। আপনি দেখতে পাচ্ছেন যে নীচের কোডটি কেবলমাত্র রুক্ষ টেম্প টেবিলের আকারটি গণনা করে (বাস্তবে, খুব রুক্ষ!): (select (SELECT count(*) FROM accomodation) * (SELECT count(*) FROM accomodation_category))তবে আপনি টেবিলের আকার আরও কাছাকাছি রাখতে এই যুক্তিকে পরিমার্জন করতে পারেন। নোট করুন যে সারিগুলি নীচে-নির্বাচনের চেয়ে ওভার-সিলেক্ট করা ভাল। উদাহরণস্বরূপ, যদি সম্ভাবনাটি খুব কম সেট করা থাকে তবে আপনি পর্যাপ্ত সারিটি না বেছে নেওয়ার ঝুঁকি নিয়ে যান।

আমাদের টেবিলের আকারটি পুনরায় গণনা করা দরকার বলে এই সমাধানটি কাসনোইয়ের সমাধানের চেয়ে ধীরে চলে। যাইহোক, আমি এই কোডিংটি অনেক বেশি পরিচালনাযোগ্য বলে মনে করি। এটি নির্ভুলতা + কার্য সম্পাদন বনাম কোডিং জটিলতার মধ্যে একটি বাণিজ্য । এটি বলার পরেও, বড় টেবিলগুলিতে এটি এখনও অর্ডার বাই র্যান্ডের () এর চেয়ে অনেক বেশি দ্রুত।

দ্রষ্টব্য: যদি ক্যোয়ারির যুক্তি অনুমতি দেয় তবে যেকোন যোগদানের ক্রিয়াকলাপের আগে যত তাড়াতাড়ি সম্ভব এলোমেলো নির্বাচন সম্পাদন করুন।


-1
function getRandomRow(){
    $id = rand(0,NUM_OF_ROWS_OR_CLOSE_TO_IT);
    $res = getRowById($id);
    if(!empty($res))
    return $res;
    return getRandomRow();
}

//rowid is a key on table
function getRowById($rowid=false){

   return db select from table where rowid = $rowid; 
}
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.