Students Save 30%! Learn & create with unlimited courses & creative assets Students Save 30%! Save Now
Advertisement
  1. Code
  2. Databases
Code

أعلى 20 + ماي أفضل الممارسات

by
Difficulty:IntermediateLength:LongLanguages:

Arabic (العربية/عربي) translation by Abdul Mutholib (you can also view the original English article)

عمليات قاعدة البيانات غالباً ما تميل إلى أن تكون العائق الرئيسي لمعظم تطبيقات الويب اليوم. وليس فقط ديسيبل (مديري قواعد البيانات) التي يجب أن تقلق بشأن هذه المسائل الأداء. كالمبرمجين علينا أن نقوم بدورنا بهيكلة الجداول بشكل صحيح، والأمثل كتابة الاستعلامات وأفضل رمز. في هذه المقالة، أنا قائمة بعض تقنيات التحسين الخلية للمبرمجين.

قبل أن نبدأ، أن تدرك أنه يمكنك العثور على طن من المفيد الخلية البرامج النصية والأدوات المساعدة على Envato السوق.

MySQL scripts and utilities on Envato Market
البرامج النصية للخلية والمرافق في الأسواق Envato

1-تحسين الاستعلامات الخاصة بك للتخزين المؤقت للاستعلام

معظم ملقمات الخلية قد الاستعلام تمكين التخزين المؤقت. أنها واحدة من الطرق الأكثر فعالية لتحسين الأداء، والتي تتم بهدوء بمشغل قاعدة البيانات. عندما يتم تنفيذ الاستعلام نفسه عدة مرات، يتم جلب النتيجة من ذاكرة التخزين المؤقت، وسريعة جداً.

المشكلة الرئيسية هي، أنها سهلة وخفية من المبرمج، ومعظمنا تميل إلى تجاهل ذلك. يمكن منع بعض الأمور التي نقوم بها فعلا في ذاكرة التخزين المؤقت للاستعلام من أداء مهمتها.

والسبب لا تعمل ذاكرة التخزين المؤقت الاستعلام في السطر الأول استخدام الدالة CURDATE(). وهذا ينطبق على جميع الوظائف غير القطعية مثل now () و rand () إلخ... حيث يمكن أن تتغير نتيجة إرجاع الدالة، تقرر الخلية لتعطيل التخزين المؤقت للاستعلام لهذا الاستعلام. كل أننا بحاجة إلى القيام به إضافة سطر إضافي من بي إتش بي قبل الاستعلام لمنع هذا من الحدوث.


2. شرح استعلامات التحديد الخاص بك

يمكن استخدام الكلمة الأساسية شرح أعطيكم فكرة عن ما تقوم به الخلية تنفيذ الاستعلام الخاص بك. وهذا يمكن أن تساعدك على بقعة الاختناقات ومشاكل أخرى مع هياكل الجدول أو الاستعلام الخاص بك.

سوف تظهر نتائج استعلام اشرح لك الفهارس التي تستخدم وكيف يتم فحص الجدول وفرزها إلخ...

أن استعلام تحديد (يفضل أن يكون معقد، مع انضمام)، وإضافة الكلمة شرح أمامه. يمكنك فقط استخدام بريس لهذا. سوف تظهر لك النتائج في جدول لطيفة. على سبيل المثال، نفترض أن نسيت لإضافة فهرس على عمود، ينضم التي أقوم بها على:

بعد إضافة الفهرس إلى ميدان group_id:

الآن بدلاً من المسح 7883 الصفوف، سوف تفحص فقط الصفوف 9 و 16 من الجداول 2. هناك قاعدة جيدة من التجربة ضرب كافة الأرقام في العمود "الصفوف"، وسيكون أداء الاستعلام الخاص بك إلى حد ما يتناسب مع الرقم الناتج.


3. الحد 1 عند الحصول على صف فريدة من نوعها

في بعض الأحيان عندما يتم الاستعلام عن الجداول الخاصة بك، كنت تعرف مسبقاً التي تبحث في صف واحد فقط. كنت قد يكون إحضار سجل فريد، أو كنت قد فقط مجرد التحقق من وجود أي عدد من السجلات التي تفي بشرط أين الخاصة بك.

في مثل هذه الحالات، يمكن زيادة إضافة 1 الحد الأقصى للاستعلام الخاص بك الأداء. وبهذه الطريقة سيتم إيقاف مشغل قاعدة بيانات المسح الضوئي للوثائق بعد ذلك يرى 1 فقط، بدلاً من الذهاب من خلال الجامعة الجدول أو الفهرس.


4. فهرس البحث عن الحقول

الفهارس ليست فقط للمفاتيح الأساسية أو مفاتيح فريدة من نوعها. إذا كان هناك أي الأعمدة في الجدول الخاص بك يمكنك سيتم البحث عن طريق، وينبغي دائماً تقريبا مؤشر لها.

كما ترون، تنطبق هذه القاعدة أيضا على بحث سلسلة جزئية مثل "اسم _ العائلة مثل' ٪ '". عند البحث من بداية السلسلة، الخلية غير قادرة على الاستفادة من الفهرس في هذا العمود.

يجب أن تفهم أيضا لا يمكن استخدام أي أنواع من عمليات التفتيش العادية الفهارس. على سبيل المثال، عند البحث عن كلمة (مثلاً أين post_content "مثل' % % أبل '")، سترى لا فائدة من وضع مؤشر عادية. سيكون من الأفضل استخدام البحث عن النص الكامل mysql أو بناء حل الفهرسة الخاص بك.


5. مؤشر واستخدام نفس أنواع الأعمدة للصلات

إذا كان التطبيق الخاص بك يحتوي على العديد من الانضمام إلى الاستعلامات، تحتاج إلى التأكد من أن الأعمدة يمكنك الانضمام إلى جانب المفهرسة في كلا الجدولين. وهذا يؤثر على كيفية الخلية داخليا يحسن عملية الربط.

أيضا، الأعمدة المنضمة، تحتاج إلى أن تكون من نفس النوع. على سبيل المثال، إذا كنت أشارك عمود عشري، إلى INT عمود من جدول آخر، الخلية ستكون غير قادر على استخدام واحد على الأقل من الفهارس. ترميزات الأحرف حتى بحاجة إلى أن تكون نفس النوع لأعمدة نوع السلسلة.


6. عدم ترتيب حسب rand)

هذا هو واحد من تلك الحيل التي تقع بارد الصوت في البداية، والعديد من المبرمجين الصاعد لهذا الفخ. قد لا يدرك ما هو نوع من اختناق رهيب يمكنك إنشاء مجرد البدء في استخدام هذا في الاستعلامات الخاصة بك.

إذا كنت حقاً بحاجة إلى صفوف عشوائية الخروج من النتائج الخاصة بك، وهناك الكثير أفضل السبل للقيام بذلك. منح يأخذ تعليمات برمجية إضافية، ولكن فسوف تمنع عنق زجاجة التي تسوء أضعافاً مضاعفة كما ينمو البيانات الخاصة بك. المشكلة هي، الخلية سوف يكون إجراء عملية rand () (الذي يستغرق تجهيز القوة) لكل صف واحد في الجدول قبل الفرز واعطائك الصف 1 فقط.

حتى يمكنك اختيار عدد عشوائي أقل من عدد النتائج، واستخدام ذلك الإزاحة في بند الحد الخاص بك.


7. تجنب تحديد *

قراءة المزيد من البيانات من الجداول، وسوف تصبح أبطأ الاستعلام. أنه يزيد من الوقت الذي يستغرقه لعمليات القرص. أيضا في حالة منفصلة من ملقم ويب ملقم قاعدة البيانات، سيكون لديك أطول شبكة التأخير بسبب البيانات التي يمكن نقلها بين الملقمات.

أنها عادة جيدة لتحديد الأعمدة التي تحتاج عندما كنت تفعل الخاص بك حدد دوماً.


8. يكاد يكون دائماً معرف الحقل

في كل جدول بعمود معرف "المفتاح الأساسي" و AUTO_INCREMENT وواحدة من نكهات INT. كما يفضل أن يكون غير الموقعة، إذ لا يمكن أن يكون القيمة سالبة.

حتى إذا كان لديك جدول مستخدمين يحتوي على حقل اسم مستخدم فريد من نوعه، لا تجعل هذا المفتاح الأساسي الخاص بك. حقول VARCHAR كمفاتيح أساسية أبطأ. وسيكون لديك هيكل أفضل في التعليمات البرمجية الخاصة بك بالإشارة إلى كافة المستخدمين مع معرف بهم داخليا.

وهناك أيضا وراء عمليات المشاهد الذي قام به محرك الخلية نفسها، يستخدم حقل المفتاح الأساسي داخليا. الذي أصبح أكثر أهمية، الأكثر تعقيداً إعداد قاعدة بيانات. (المجموعات، تقسيم إلخ...).

واحدة استثناء محتمل للقاعدة هي "الجداول رابطة"، المستخدمة لنوع العديد-للعديد من الجمعيات بين الجداول 2. على سبيل المثال جدول "posts_tags" الذي يحتوي على أعمدة 2: post_id، tag_id، الذي يتم استخدامه للعلاقات بين اثنين من الجداول يسمى "ما بعد" و "العلامات". هذه الجداول يمكن أن يكون مفتاح أساسي الذي يحتوي على حقول المعرف على حد سواء.


9. استخدام التعداد عبر VARCHAR

أعمدة نوع ENUM سريعة جداً والاتفاق. داخليا تم تخزينها مثل TINYINT، إلا أنها يمكن أن تحتوي على وعرض قيم السلسلة. وهذا يجعلها مرشح مثالي لحقول معينة.

إذا كان لديك أحد الحقول التي سوف تحتوي على عدد قليل فقط من أنواع مختلفة من القيم، استخدم التعداد بدلاً من VARCHAR. على سبيل المثال، يمكن عمود يسمى "الحالة"، وتحتوي فقط على قيم مثل "نشط"، "غير نشط"، "معلق"، "منتهية الصلاحية" إلخ...

حتى أن هناك طريقة للحصول على "اقتراح" من الخلية نفسها على كيفية إعادة هيكلة الجدول الخاص بك. عندما يكون لديك حقل VARCHAR، أنها يمكن أن توحي فعلا بتغيير نوع هذا العمود للتعداد بدلاً من ذلك. هذا تم استخدام استدعاء الإجراء ANALYSE(). الذي يقودنا إلى:


10. الحصول على اقتراحات بإجراء ANALYSE()

وسوف تتيح ANALYSE() الداخلي الخلية تحليل هياكل الأعمدة والبيانات الفعلية في الجدول الخاص بك الخروج باقتراحات معينة لك. إلا أنها مفيدة إذا كان هناك بيانات فعلية في الجداول الخاصة بك نظراً لأن يقوم بدور كبير في عملية صنع القرار.

على سبيل المثال، إذا قمت بإنشاء حقل INT للمفتاح الأساسي الخاص بك، ولكن لا يحتوي على صفوف كثيرة جداً، قد اقترح عليك استخدام MEDIUMINT بدلاً من ذلك. أو إذا كنت تستخدم حقل VARCHAR، قد تحصل على اقتراح بتحويله إلى التعداد، إذا كان هناك فقط عدد قليل من القيم الفريدة.

يمكنك أيضا تشغيل هذا بواسطة النقر فوق الارتباط "اقتراح بنية الجدول" بريس، في واحدة من طرق عرض الجدول الخاص بك.

تبقى في الاعتبار هذه هي الاقتراحات فقط. وإذا كان الجدول الخاص بك سوف تنمو وتكبر، قد لا يكون الاقتراحات الصحيحة لمتابعة. القرار الذي هو في نهاية المطاف لك.


11-استخدام غير فارغة إذا كنت تستطيع

دائماً ما لم يكن لديك سبب محدد جداً لاستخدام قيمة فارغة (null)، يجب عليك تعيين الأعمدة الخاصة بك كما NOT NULL.

أولاً وقبل كل شيء، اسأل نفسك ما إذا كان هناك أي اختلاف بين وجود قيمة سلسلة فارغة مقابل قيمة خالية NULL (بالنسبة لحقول INT: 0 مقابل NULL). إذا لم يكن هناك أي سبب ليكون على حد سواء، لا تحتاج حقل فارغة (null). (هل تعلم أن أوراكل تعتبر فارغة (null) وسلسلة فارغة كنفسه؟)

أعمدة فارغة تتطلب مساحة إضافية ويمكنهم إضافة تعقيد لمقارنة البيانات الخاصة بك. تجنب لهم فقط عندما تستطيع. ومع ذلك، أنا أفهم بعض الناس قد يكون أسباب محددة للغاية لتحتوي على قيم فارغة (null)، التي ليست دائماً شيئا سيئاً.

من مستندات الخلية:

"أعمدة فارغة تتطلب مساحة إضافية في الصف لتسجيل ما إذا كانت القيم الخاصة بها هي NULL. بالنسبة للجداول MyISAM، كل عمود NULL يأخذ بت واحد إضافي، تقريبه إلى أقرب البايت ".


12.  إعداد البيانات

وهناك فوائد متعددة لاستخدام البيانات المعدة، سواء بالنسبة للأداء وأسباب أمنية.

يقوم بتصفية البيانات المعدة المتغيرات التي قمت بربط لهم بشكل افتراضي، وعظيم لحماية التطبيق الخاص بك ضد الهجمات مزود الحقن. يمكنك بطبيعة الحال تصفية المتغيرات الخاصة بك يدوياً أيضا، ولكن تلك الأساليب أكثر عرضه للخطأ البشري والسهو من قبل المبرمج. وهذا أقل من قضية عند استخدام نوع الإطار أو ORM.

نظراً لتركيزنا على الأداء، أود أيضا أن أذكر الفوائد في هذا المجال. هذه الفوائد أكثر أهمية عندما يتم استخدام نفس الاستعلام عدة مرات في التطبيق الخاص بك. يمكنك تعيين قيم مختلفة لنفس البيان استعداد، ومع ذلك الخلية سوف يكون فقط لتحليل مرة واحدة.

كما أحدث الإصدارات من الخلية ينقل البيانات المعدة في شكل ثنائي أصلية، التي هي أكثر كفاءة ويمكن أن يساعد أيضا في الحد من التأخيرات في الشبكة.

كان هناك وقت عندما تستخدم العديد من المبرمجين لتجنب البيانات المعدة عن قصد، لسبب هام واحد. قد لا يتم التخزين المؤقت من ذاكرة التخزين المؤقت الاستعلام الخلية. ولكن منذ بعض الوقت حول الإصدار 5.1، التخزين المؤقت للاستعلام معتمد جداً.

لاستخدام البيانات المعدة في بي يمكنك التحقق من ملحق mysqli أو استخدام طبقة تجريد قاعدة بيانات مثل شركة تنمية نفط عمان.


13. يمكن الاستعلامات

عادة عند تنفيذ استعلام من برنامج نصي, سوف ننتظر لتنفيذ هذا الاستعلام لإنهاء قبل أن يمكن أن تستمر. يمكنك تغيير هذا باستخدام استعلامات يمكن.

وهناك تفسير كبير في مستندات PHP للدالة mysql_unbuffered_query():

"mysql_unbuffered_query() يرسل الاستعلام استعلام SQL إلى الخلية دون جلب والتخزين المؤقت الصفوف النتيجة كما يفعل mysql_query() تلقائياً. وهذا يوفر قدرا كبيرا من الذاكرة مع استعلامات SQL التي تنتج مجموعات نتائج كبيرة، ويمكنك أن تبدأ العمل على النتيجة فورا بعد الصف الأول قد تم استردادها كما لم يكن لديك إلى الانتظار حتى تم تم تنفيذ استعلام SQL كاملة. "

ومع ذلك، يأتي مع بعض القيود. يجب عليك قراءة كافة الصفوف أو استدعاء mysql_free_result() قبل أن يمكنك تنفيذ استعلام آخر. كما لا يحق لك استخدام mysql_num_rows() أو mysql_data_seek() في مجموعة النتائج.


14. تخزين عناوين IP ك INT غير الموقعة

سيتم إنشاء العديد من المبرمجين حقل VARCHAR(15) دون أن يدركوا أنهم في الواقع تخزين عناوين IP كقيم الإعداد الصحيحة. مع INT تذهب وصولاً إلى 4 بايت فقط من المساحة، وتحتوي على حقل حجم ثابت بدلاً من ذلك.

لديك للتأكد من العمود الخاص بك هو INT غير موقعة، لأن عناوين IP استخدام النطاق الكامل لعدد صحيح 32 بت غير موقعة.

في الاستعلامات الخاصة بك يمكنك استخدام INET_ATON() لتحويل والملكية الفكرية إلى عدد صحيح، و INET_NTOA() للعكس. وهناك أيضا وظائف مماثلة في بي تسمى ip2long() و long2ip().


15. ثابت (ثابت) طول الجداول هي أسرع

عندما يكون كل عمود واحد في جدول "ذات طول ثابت"، كما يعتبر الجدول "ثابتة" أو "ذات طول ثابت". أمثلة لأنواع الأعمدة التي ليست ذات طول ثابت: BLOB VARCHAR، نص،. إذا قمت بتضمين 1 حتى مجرد لهذه الأنواع من الأعمدة، الجدول تتوقف عن أن تكون ذات طول ثابت ويمكن معالجتها بشكل مختلف من محرك الخلية.

الجداول ذات طول ثابت يمكن تحسين الأداء لأنه أسرع لمحرك الخلية البحث خلال السجلات. عندما أرادت قراءة صف محدد في جدول، فإنه يمكن حساب بسرعة موقف منه. إذا لم يتم إصلاح حجم الصف، في كل مرة أنها بحاجة للقيام البحث، قد أن يتشاور مع فهرس المفتاح الأساسي.

كما أنها أسهل في ذاكرة التخزين المؤقت وأسهل لإعادة إعمار بعد حادث تحطم طائرة. لكنها أيضا يمكن أن تأخذ مساحة أكبر. على سبيل المثال، إذا قمت بتحويل حقل VARCHAR(20) لحقل CHAR(20)، دائماً سيستغرق 20 بايت مساحة بغض النظر عن ما عليه في.

باستخدام تقنيات "التقسيم الرأسي"، يمكنك فصل أعمدة متغيرة الطول إلى جدول منفصل. الذي يقودنا إلى:


16. تقسيم عمودي

التقسيم الرأسي هو قانون لتقسيم بنية الجدول الخاص بك بطريقة رأسية لأسباب الأمثل.

مثال 1: قد يكون لديك جدول مستخدمين يحتوي على عناوين المنازل، التي لم تحصل على قراءة غالباً. يمكنك اختيار لتقسيم الجدول الخاص بك وتخزين معلومات العنوان في جدول منفصل. وبهذه الطريقة سوف تتقلص الجدول الخاص بك المستخدمين الرئيسيين في الحجم. وكما تعلمون، أداء الجداول أصغر حجماً بشكل أسرع.

مثال 2: لديك حقل "last_login" في الجدول الخاص بك. أنه يقوم بتحديث كل مرة مستخدم بتسجيل في الموقع. ولكن أسباب كل تحديث على جدول الاستعلام ذاكرة التخزين المؤقت لهذا الجدول مسح. يمكنك وضع هذا الحقل في جدول آخر للاحتفاظ بالتحديثات إلى جدول المستخدمين الخاصة بك إلى الحد أدنى.

ولكن تحتاج أيضا إلى التأكد من لا تحتاج باستمرار إلى الانضمام إلى هذه الجداول 2 بعد تقسيم أو أنك قد تعاني فعلا انخفاض الأداء.


17. تقسيم حذف كبيرة أو استعلامات إدراج

إذا كنت بحاجة لإجراء استعلام حذف أو إدراج كبيرة على موقع على شبكة الإنترنت مباشرة، عليك أن تكون حريصا على عدم الإخلال بحركة المرور على الشبكة. عند تنفيذ استعلام كبيرة مثل هذا، ويمكن قفل الجداول الخاصة بك وإحضار تطبيق الويب الخاص بك إلى وقف.

أباتشي تشغيل مؤشرات العمليات المتوازية كثيرة. ولذلك يعمل أكثر كفاءة عند إنهاء البرامج النصية للتنفيذ في أقرب وقت ممكن، حتى الملقمات لا تواجه الكثير من الاتصالات المفتوحة والعمليات في وقت واحد التي تستهلك الموارد، لا سيما الذاكرة.

إذا كنت في نهاية المطاف تأمين الجداول الخاصة بك لأي فترة ممتدة من الزمن (مثل 30 ثانية أو أكثر)، في موقع حركة مرور عالية، كنت سوف يسبب تصادم العملية والاستعلام، الذي قد يستغرق وقتاً طويلاً لمسح أو حتى تعطل ملقم ويب الخاص بك.

إذا كان لديك نوع من البرنامج النصي للصيانة التي تحتاج إلى حذف عدد كبير من الصفوف، مجرد استخدام شرط الحد الأقصى للقيام بذلك في مجموعات صغيرة لتجنب هذا الازدحام.


18. أصغر الأعمدة أسرع

مع محركات قواعد البيانات، القرص هو ربما أهم عنق الزجاجة. حفظ الأشياء أصغر وأكثر أحكاما عادة ما تكون مفيدة من حيث الأداء، وتقليل كمية نقل القرص.

وقد مستندات الخلية قائمة "متطلبات التخزين" لكافة أنواع البيانات.

إذا كان يتوقع جدول يحتوي على صفوف قليلة جداً، ليس هناك سبب لجعل المفتاح الأساسي INT، بدلاً من MEDIUMINT، SMALLINT أو حتى في بعض الحالات TINYINT. إذا كنت لا تحتاج عنصر الوقت، استخدام التاريخ بدلاً من التاريخ والوقت.

فقط تأكد من ترك مساحة معقولة تنمو أو قد ينتهي بك الأمر مثل Slashdot.


19. اختر مشغل التخزين الصحيح

اثنين من محركات التخزين الرئيسي في الخلية هي MyISAM و InnoDB. يكون لكل الإيجابيات والسلبيات الخاصة بهم.

MyISAM جيد لتطبيقات القراءة الثقيلة، ولكن لا أنه مقياس جيد للغاية عندما يكون هناك الكثير من الكتابات. حتى إذا كنت تقوم بتحديث حقل واحد من صف واحد، يحصل على تأمين الجدول بأكمله، وحتى يمكن قراءة أي عملية أخرى منه حتى أن يتم الانتهاء من الاستعلام. MyISAM بسرعة جداً في حساب COUNT(*) حدد أنواع الاستعلامات.

InnoDB يميل إلى أن يكون محرك تخزين أكثر تعقيداً، ويمكن أن يكون أبطأ من MyISAM لمعظم التطبيقات الصغيرة. بل أنه يدعم تأمين يستند إلى الصف، الذي يقيس أفضل. كما أنها تدعم بعض الميزات الأكثر تقدما مثل المعاملات.


20. استخدام مخطط كائن العلائقية

باستخدام ORM (كائن رسم الخرائط العلائقية)، يمكنك الحصول على بعض مزايا الأداء. ORM كل ما يمكن القيام به، يمكن أن يكون ترميز يدوياً أيضا. ولكن هذا يعني الكثير من العمل الإضافي ويتطلب مستوى عال من الخبرة.

ORM لكبيرة ل "تحميل كسول". وهذا يعني أنها يمكن إحضار قيم إلا حيث تكون هناك حاجة إليها. ولكن عليك أن تكون حذراً معهم أو يمكنك في نهاية المطاف خلق للعديد من الاستعلامات البسيطة التي يمكن أن تقلل من الأداء.

أيضا دفعة ل ORM في الاستعلامات الخاصة بك في المعاملات، التي تعمل أسرع بكثير من إرسال الاستعلامات الفردية إلى قاعدة البيانات.

حاليا بي ORM المفضلة لبى هو المذهب. لقد كتبت مقالة عن كيفية تثبيت المذهب مع CodeIgniter.


21. أن تكون حذراً مع الاتصالات المستمرة

الاتصالات المستمرة تهدف إلى الحد من النفقات العامة لإعادة إنشاء اتصالات للخلية. عندما يتم إنشاء اتصال مستمر، أنها ستبقى مفتوحة حتى بعد انتهاء البرنامج النصي قيد التشغيل. منذ أباتشي يعيد استخدام العمليات التابعة، المرة القادمة العملية تشغيل لبرنامج نصي جديد، سوف إعادة استخدام نفس اتصال الخلية.

يبدو كبيرا من الناحية النظرية. ولكن من تجربتي الشخصية (وكثير غيرها)، يتميز هذا تبين أنه لا يستحق عناء. يمكن أن لديك مشاكل خطيرة مع حدود اتصال، مشكلات الذاكرة وهلم جرا.

أباتشي تشغيل الغاية موازية، ويخلق العديد من العمليات التابعة. وهذا هو السبب الرئيسي في أن الاتصالات المستمرة لا تعمل جيدا في هذه البيئة. قبل أن تفكر باستخدام الدالة mysql_pconnect()، استشر المشرف النظام الخاص بك.


Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.