Android, verimli bir SQL veritabanı olan SQLite için yerleşik destek sunar. Uygulamanızın performansını optimize etmek için bu en iyi uygulamalardan yararlanarak, verileriniz büyüdükçe uygulamanızın hızlı ve öngörülebilir şekilde hızlı kalmasını sağlayın. Bu en iyi uygulamaları kullanarak, yeniden oluşturması ve gidermesi zor olan performans sorunlarıyla karşılaşma olasılığını da azaltırsınız.
Daha hızlı bir performans elde etmek için aşağıdaki performans ilkelerini uygulayın:
Daha az satır ve sütun okuyun: Sorgularınızı yalnızca gerekli verileri alacak şekilde optimize edin. Aşırı veri alımı performansı etkileyebileceğinden veritabanından okunan veri miktarını en aza indirin.
İşleri SQLite Engine'e aktarma: SQL sorguları içinde hesaplama, filtreleme ve sıralama işlemleri gerçekleştirin. SQLite'ın sorgu motorunu kullanmak performansı önemli ölçüde artırabilir.
Veritabanı şemasını değiştirin: SQLite'ın verimli sorgu planları ve veri temsilleri oluşturmasına yardımcı olmak için veritabanı şemanızı tasarlayın. Performansı artırmak için tabloları uygun şekilde dizine ekleyin ve tablo yapılarını optimize edin.
Ayrıca, optimizasyon gerektiren alanları belirlemek için SQLite veritabanınızın performansını ölçmek amacıyla mevcut sorun giderme araçlarından yararlanabilirsiniz.
Jetpack Room kitaplığını kullanmanızı öneririz.
Veritabanını performans için yapılandırma
Veritabanınızı SQLite'ta optimum performans sağlayacak şekilde yapılandırmak için bu bölümdeki adımları uygulayın.
Yazma Öncesi Günlük Kaydını Etkinleştir
SQLite, mutasyonları bir günlüğe ekleyerek uygular ve bunları bazen veritabanına sıkıştırır. Buna Write-Ahead Logging (WAL) (Yazma Önceden Günlük Kaydı) adı verilir.
ATTACH
DATABASE
kullanmıyorsanız WAL'ı etkinleştirin.
Senkronizasyon modunu gevşetme
WAL kullanırken varsayılan olarak her kaydetme, verilerin diske ulaştığından emin olmak için bir fsync
gönderir. Bu, veri dayanıklılığını artırır ancak taahhütlerinizi yavaşlatır.
SQLite'ta eşzamanlı modu kontrol etme seçeneği bulunur. WAL'ı etkinleştirirseniz eşzamanlı modu NORMAL
olarak ayarlayın:
Kotlin
db.execSQL("PRAGMA synchronous = NORMAL")
Java
db.execSQL("PRAGMA synchronous = NORMAL");
Bu ayarda, bir kaydetme, veriler diskte depolanmadan önce döndürülebilir. Güç kaybı veya çekirdek paniği gibi bir cihaz kapanması durumunda, taahhüt edilen veriler kaybolabilir. Ancak günlük kaydı sayesinde veritabanınız bozulmamıştır.
Yalnızca uygulamanız kilitlenirse verileriniz diske ulaşmaya devam eder. Çoğu uygulama için bu ayar, önemli bir maliyet olmaksızın performans iyileştirmeleri sağlar.
Verimli tablo şemaları tanımlama
Performansı optimize etmek ve veri tüketimini en aza indirmek için verimli bir tablo şeması tanımlayın. SQLite, verimli sorgu planları ve veriler oluşturarak veri alma işlemini hızlandırır. Bu bölümde, tablo şemaları oluşturmayla ilgili en iyi uygulamalar açıklanmaktadır.
INTEGER PRIMARY KEY
uygulamasını düşünün
Bu örnekte, bir tabloyu aşağıdaki gibi tanımlayıp doldurun:
CREATE TABLE Customers(
id INTEGER,
name TEXT,
city TEXT
);
INSERT INTO Customers Values(456, 'John Lennon', 'Liverpool, England');
INSERT INTO Customers Values(123, 'Michael Jackson', 'Gary, IN');
INSERT INTO Customers Values(789, 'Dolly Parton', 'Sevier County, TN');
Tablo çıktısı aşağıdaki gibidir:
satır kimliği | id | ad | şehir |
---|---|---|---|
1 | 456 | daha fazla içerik | Liverpool, İngiltere |
2 | 123 | Michael Jackson'ın yer aldığı daha fazla içerik | Gary, Indiana |
3 | 789 | Dolly Parton | Sevier İlçesi, New York |
rowid
sütunu, kampanya siparişini koruyan bir dizindir. rowid
ölçütüne göre filtrelenen sorgular hızlı bir B ağacı araması olarak uygulanır ancak id
ölçütüne göre filtrelenen sorgular yavaş bir tablo taramasıdır.
id
tarihine kadar arama yapmayı planlıyorsanız rowid
sütununu depolama alanında daha az veri ve genel olarak daha hızlı bir veritabanı için depolamaktan kaçınabilirsiniz:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
city TEXT
);
Tablonuz artık aşağıdaki gibi görünür:
id | ad | şehir |
---|---|---|
123 | Michael Jackson'ın yer aldığı daha fazla içerik | Gary, Indiana |
456 | daha fazla içerik | Liverpool, İngiltere |
789 | Dolly Parton | Sevier İlçesi, New York |
rowid
sütununu depolamanız gerekmediği için id
sorguları hızlıdır. Tablonun artık kampanya siparişi yerine id
ölçütüne göre sıralandığını unutmayın.
Dizinleri kullanarak sorguları hızlandırın
SQLite, sorguları hızlandırmak için dizinleri kullanır. Bir sütunu filtrelerken (WHERE
), sıralarken (ORDER BY
) veya toplarken (GROUP BY
) tabloda sütun için bir dizin varsa sorgu hızlandırılır.
Önceki örnekte, city
ölçütüne göre filtreleme yapmak için tablonun tamamının taranmasını gerektirir.
SELECT id, name
WHERE city = 'London, England';
Çok sayıda şehir sorgusu içeren bir uygulamada bu sorguları bir dizinle hızlandırabilirsiniz:
CREATE INDEX city_index ON Customers(city);
Dizin, ek bir tablo olarak uygulanır, dizin sütununa göre sıralanır ve rowid
ile eşlenir:
şehir | satır kimliği |
---|---|
Gary, Indiana | 2 |
Liverpool, İngiltere | 1 |
Sevier İlçesi, New York | 3 |
Artık hem orijinal tabloda hem de dizinde mevcut olduğundan city
sütununun depolama maliyetinin iki katına çıktığını unutmayın. Dizini kullandığınız için daha hızlı sorguların avantajı, ek depolama alanının maliyetine değecektir.
Ancak sorgu performansı kazancı olmadan depolama alanı maliyetini ödemekten kaçınmak için, kullanmadığınız bir dizini tutmayın.
Çok sütunlu dizinler oluşturma
Sorgularınız birden çok sütunu birleştiriyorsa sorguyu tamamen hızlandırmak için çok sütunlu dizinler oluşturabilirsiniz. Ayrıca, dış bir sütunda bir dizin kullanıp iç aramanın doğrusal bir tarama olarak yapılmasını sağlayabilirsiniz.
Örneğin, aşağıdaki sorguya göre:
SELECT id, name
WHERE city = 'London, England'
ORDER BY city, name
Sorguyu, sorguda belirtilen sırayla çok sütunlu bir dizinle hızlandırabilirsiniz:
CREATE INDEX city_name_index ON Customers(city, name);
Bununla birlikte, yalnızca city
üzerinde bir dizininiz varsa dış sipariş yine de hızlandırılırken iç sıralama için doğrusal tarama gerekir.
Bu yöntem ön ek sorguları için de kullanılabilir. Örneğin, bir dizin ON Customers (city, name)
çok sütunlu bir dizine ait dizin tablosu, verilen dizinlere göre belirtilen sırada yer aldığı için city
ölçütüne göre filtreleme, sıralama ve gruplandırmayı da hızlandırır.
WITHOUT ROWID
uygulamasını düşünün
Varsayılan olarak SQLite, tablonuz için bir rowid
sütunu oluşturur. Burada rowid
, örtülü bir INTEGER PRIMARY KEY AUTOINCREMENT
'dir. INTEGER PRIMARY KEY
olan bir sütununuz varsa bu sütun rowid
için takma ad olur.
INTEGER
dışında bir birincil anahtarı veya sütunların bir bileşimini içeren tablolar için WITHOUT
ROWID
seçeneğini değerlendirin.
Küçük verileri BLOB
, büyük verileri ise dosya olarak depolayın
Büyük verileri bir satırla (ör. bir resmin küçük resmi veya kişi fotoğrafı) ilişkilendirmek isterseniz verileri bir BLOB
sütununda veya bir dosyada depolayabilir, ardından dosya yolunu sütunda depolayabilirsiniz.
Dosyalar genellikle 4 KB'lık artışlara yuvarlanır. Yuvarlama hatasının önemli olduğu çok küçük dosyalarda, bunların veritabanında BLOB
olarak depolanması daha verimlidir. SQLite, dosya sistemi çağrılarını en aza indirir ve bazı durumlarda temel dosya sisteminden daha hızlıdır.
Sorgu performansını iyileştirme
Yanıt sürelerini en aza indirip işleme verimliliğini en üst düzeye çıkararak SQLite'ta sorgu performansını iyileştirmek için bu en iyi uygulamaları izleyin.
Yalnızca ihtiyacınız olan satırları okuyun
Filtreler; tarih aralığı, konum veya ad gibi belirli ölçütleri belirterek sonuçlarınızın kapsamını daraltmanıza olanak tanır. Sınırlar, gördüğünüz sonuçların sayısını kontrol etmenize olanak tanır:
Kotlin
db.rawQuery(""" SELECT name FROM Customers LIMIT 10; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT name FROM Customers LIMIT 10; """, null)) { while (cursor.moveToNext()) { ... } }
Yalnızca ihtiyacınız olan sütunları okuyun
Gereksiz sütunlar seçmekten kaçının. Bu, sorgularınızı yavaşlatabilir ve kaynakları boşa harcayabilir. Bunun yerine, yalnızca kullanılan sütunları seçin.
Aşağıdaki örnekte id
, name
ve phone
değerini seçtiniz:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery( """ SELECT id, name, phone FROM customers; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { val name = cursor.getString(1) // ... } }
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT id, name, phone FROM customers; """, null)) { while (cursor.moveToNext()) { String name = cursor.getString(1); ... } }
Ancak, yalnızca name
sütununa ihtiyacınız vardır:
Kotlin
db.rawQuery(""" SELECT name FROM Customers; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { val name = cursor.getString(0) ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT name FROM Customers; """, null)) { while (cursor.moveToNext()) { String name = cursor.getString(0); ... } }
Benzersiz değerler için DISTINCT
kullanın
DISTINCT
anahtar kelimesini kullanmak, işlenmesi gereken veri miktarını azaltarak sorgularınızın performansını artırabilir. Örneğin, yalnızca bir sütundaki benzersiz değerleri döndürmek istiyorsanız DISTINCT
değerini kullanın:
Kotlin
db.rawQuery(""" SELECT DISTINCT name FROM Customers; """.trimIndent(), null ).use { cursor -> while (cursor.moveToNext()) { // Only iterate over distinct names in Kotlin ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT DISTINCT name FROM Customers; """, null)) { while (cursor.moveToNext()) { // Only iterate over distinct names in Java ... } }
Mümkün olduğunda toplama işlevlerini kullanın
Satır verileri olmadan toplu sonuçlar için toplama işlevlerini kullanın. Örneğin, aşağıdaki kod, eşleşen en az bir satır olup olmadığını kontrol eder:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery(""" SELECT id, name FROM Customers WHERE city = 'Paris'; """.trimIndent(), null ).use { cursor -> if (cursor.moveToFirst()) { // At least one customer from Paris ... } else { // No customers from Paris ... }
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT id, name FROM Customers WHERE city = 'Paris'; """, null)) { if (cursor.moveToFirst()) { // At least one customer from Paris ... } else { // No customers from Paris ... } }
Yalnızca ilk satırı getirmek amacıyla, eşleşen satır yoksa 0
değerini döndürmek için EXISTS()
ve bir veya daha fazla satır eşleşiyorsa 1
kullanabilirsiniz:
Kotlin
db.rawQuery(""" SELECT EXISTS ( SELECT null FROM Customers WHERE city = 'Paris'; ); """.trimIndent(), null ).use { cursor -> if (cursor.moveToFirst() && cursor.getInt(0) == 1) { // At least one customer from Paris ... } else { // No customers from Paris ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT EXISTS ( SELECT null FROM Customers WHERE city = 'Paris' ); """, null)) { if (cursor.moveToFirst() && cursor.getInt(0) == 1) { // At least one customer from Paris ... } else { // No customers from Paris ... } }
Uygulama kodunuzda SQLite toplama işlevlerini kullanın:
COUNT
: Bir sütunda kaç satır olduğunu sayar.SUM
: Bir sütundaki tüm sayısal değerleri toplar.MIN
veyaMAX
: En düşük veya en yüksek değeri belirler. Sayısal sütunlar,DATE
türleri ve metin türlerinde çalışır.AVG
: Ortalama sayısal değeri bulur.GROUP_CONCAT
: Dizeleri isteğe bağlı bir ayırıcıyla birleştirir.
Cursor.getCount()
yerine COUNT()
kullan
Aşağıdaki örnekte Cursor.getCount()
işlevi, veritabanındaki tüm satırları okur ve tüm satır değerlerini döndürür:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery(""" SELECT id FROM Customers; """.trimIndent(), null ).use { cursor -> val count = cursor.getCount() }
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT id FROM Customers; """, null)) { int count = cursor.getCount(); ... }
Ancak COUNT()
kullanıldığında veritabanı yalnızca şu sayıyı döndürür:
Kotlin
db.rawQuery(""" SELECT COUNT(*) FROM Customers; """.trimIndent(), null ).use { cursor -> cursor.moveToFirst() val count = cursor.getInt(0) }
Java
try (Cursor cursor = db.rawQuery(""" SELECT COUNT(*) FROM Customers; """, null)) { cursor.moveToFirst(); int count = cursor.getInt(0); ... }
Kod yerine Nest sorguları
SQL, oluşturulabilme özelliğine sahiptir ve alt sorguları, birleştirmeleri ve yabancı anahtar kısıtlamalarını destekler. Bir sorgunun sonucunu, uygulama kodunu kullanmadan başka bir sorguda kullanabilirsiniz. Bu, SQLite'tan veri kopyalama ihtiyacını azaltır ve veritabanı motorunun sorgunuzu optimize etmesini sağlar.
Aşağıdaki örnekte, en çok müşterinin hangi şehirde olduğunu bulmak için bir sorgu çalıştırabilir ve ardından sonucu bu şehirdeki tüm müşterileri bulmak için başka bir sorguda kullanabilirsiniz:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery(""" SELECT city FROM Customers GROUP BY city ORDER BY COUNT(*) DESC LIMIT 1; """.trimIndent(), null ).use { cursor -> if (cursor.moveToFirst()) { val topCity = cursor.getString(0) db.rawQuery(""" SELECT name, city FROM Customers WHERE city = ?; """.trimIndent(), arrayOf(topCity)).use { innerCursor -> while (innerCursor.moveToNext()) { ... } } } }
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT city FROM Customers GROUP BY city ORDER BY COUNT(*) DESC LIMIT 1; """, null)) { if (cursor.moveToFirst()) { String topCity = cursor.getString(0); try (Cursor innerCursor = db.rawQuery(""" SELECT name, city FROM Customers WHERE city = ?; """, new String[] {topCity})) { while (innerCursor.moveToNext()) { ... } } } }
Sonucu bir önceki örneğin yarısında almak için iç içe yerleştirilmiş ifadelere sahip tek bir SQL sorgusu kullanın:
Kotlin
db.rawQuery(""" SELECT name, city FROM Customers WHERE city IN ( SELECT city FROM Customers GROUP BY city ORDER BY COUNT (*) DESC LIMIT 1; ); """.trimIndent(), null ).use { cursor -> if (cursor.moveToNext()) { ... } }
Java
try (Cursor cursor = db.rawQuery(""" SELECT name, city FROM Customers WHERE city IN ( SELECT city FROM Customers GROUP BY city ORDER BY COUNT(*) DESC LIMIT 1 ); """, null)) { while(cursor.moveToNext()) { ... } }
SQL'de benzersizliği kontrol etme
Tabloda belirli bir sütun değeri benzersiz olmadığı sürece bir satırın eklenmemesi gerekiyorsa bu benzersizliğin sütun kısıtlaması olarak uygulanması daha verimli olabilir.
Aşağıdaki örnekte, eklenecek satırı doğrulamak için bir sorgu ve gerçekten eklenecek başka bir sorgu çalıştırılmıştır:
Kotlin
// This is not the most efficient way of doing this. // See the following example for a better approach. db.rawQuery( """ SELECT EXISTS ( SELECT null FROM customers WHERE username = ? ); """.trimIndent(), arrayOf(customer.username) ).use { cursor -> if (cursor.moveToFirst() && cursor.getInt(0) == 1) { throw AddCustomerException(customer) } } db.execSQL( "INSERT INTO customers VALUES (?, ?, ?)", arrayOf( customer.id.toString(), customer.name, customer.username ) )
Java
// This is not the most efficient way of doing this. // See the following example for a better approach. try (Cursor cursor = db.rawQuery(""" SELECT EXISTS ( SELECT null FROM customers WHERE username = ? ); """, new String[] { customer.username })) { if (cursor.moveToFirst() && cursor.getInt(0) == 1) { throw new AddCustomerException(customer); } } db.execSQL( "INSERT INTO customers VALUES (?, ?, ?)", new String[] { String.valueOf(customer.id), customer.name, customer.username, });
Kotlin veya Java'daki benzersiz kısıtlamayı kontrol etmek yerine, tabloyu tanımlarken SQL'de kontrol edebilirsiniz:
CREATE TABLE Customers(
id INTEGER PRIMARY KEY,
name TEXT,
username TEXT UNIQUE
);
SQLite aşağıdaki işlemleri yapar:
CREATE TABLE Customers(...);
CREATE UNIQUE INDEX CustomersUsername ON Customers(username);
Artık bir satır ekleyebilir ve SQLite'ın kısıtlamayı kontrol etmesini sağlayabilirsiniz:
Kotlin
try { db.execSql( "INSERT INTO Customers VALUES (?, ?, ?)", arrayOf(customer.id.toString(), customer.name, customer.username) ) } catch(e: SQLiteConstraintException) { throw AddCustomerException(customer, e) }
Java
try { db.execSQL( "INSERT INTO Customers VALUES (?, ?, ?)", new String[] { String.valueOf(customer.id), customer.name, customer.username, }); } catch (SQLiteConstraintException e) { throw new AddCustomerException(customer, e); }
SQLite, birden çok sütunlu benzersiz dizinleri destekler:
CREATE TABLE table(...);
CREATE UNIQUE INDEX unique_table ON table(column1, column2, ...);
SQLite, kısıtlamaları Kotlin veya Java koduna göre daha hızlı ve daha az ek yük ile doğrular. Uygulama kodu yerine SQLite kullanmak en iyi uygulamalardan biridir.
Birden fazla eklemeyi tek bir işlemde toplu hale getirme
Bir işlem birden çok işlem gerçekleştirir ve bu yalnızca verimliliği değil, doğruluğu da artırır. Veri tutarlılığını iyileştirmek ve performansı hızlandırmak için eklemeleri toplu olarak yapabilirsiniz:
Kotlin
db.beginTransaction() try { customers.forEach { customer -> db.execSql( "INSERT INTO Customers VALUES (?, ?, ...)", arrayOf(customer.id.toString(), customer.name, ...) ) } } finally { db.endTransaction() }
Java
db.beginTransaction(); try { for (customer : Customers) { db.execSQL( "INSERT INTO Customers VALUES (?, ?, ...)", new String[] { String.valueOf(customer.id), customer.name, ... }); } } finally { db.endTransaction() }
Sorun giderme araçlarını kullanma
SQLite, performansı ölçmenize yardımcı olmak için aşağıdaki sorun giderme araçlarını sağlar.
SQLite'ın etkileşimli istemini kullanın
Sorguları çalıştırmak ve öğrenmek için makinenizde SQLite'ı çalıştırın.
Farklı Android platform sürümleri, SQLite'ın farklı düzeltmelerini kullanır. Android destekli bir cihazdaki aynı motoru kullanmak için adb shell
kullanarak hedef cihazınızda sqlite3
komutunu çalıştırın.
SQLite'tan sorguları zamanlamasını isteyebilirsiniz:
sqlite> .timer on
sqlite> SELECT ...
Run Time: real ... user ... sys ...
EXPLAIN QUERY PLAN
SQLite'tan, EXPLAIN QUERY PLAN
kullanarak bir sorguyu nasıl yanıtlamayı hedeflediğini açıklamasını isteyebilirsiniz:
sqlite> EXPLAIN QUERY PLAN
SELECT id, name
FROM Customers
WHERE city = 'Paris';
QUERY PLAN
`--SCAN Customers
Önceki örnekte, Paris'teki tüm müşterileri bulmak için dizin olmadan tam tablo taraması gerekir. Buna doğrusal karmaşıklık denir. SQLite'ın tüm satırları okuması ve yalnızca Paris'teki müşterilerle eşleşen satırları tutması gerekiyor. Bunu düzeltmek için bir dizin ekleyebilirsiniz:
sqlite> CREATE INDEX Idx1 ON Customers(city);
sqlite> EXPLAIN QUERY PLAN
SELECT id, name
FROM Customers
WHERE city = 'Paris';
QUERY PLAN
`--SEARCH test USING INDEX Idx1 (city=?
Etkileşimli kabuk kullanıyorsanız SQLite'tan sorgu planlarını her zaman açıklamasını isteyebilirsiniz:
sqlite> .eqp on
Daha fazla bilgi için Sorgu Planlama bölümüne bakın.
SQLite Analiz Aracı
SQLite, performans sorunlarını gidermek için kullanılabilecek ek bilgilerin dökümü için sqlite3_analyzer
komut satırı arayüzünü (CLI) sunar. Yüklemek için SQLite İndirme Sayfasını ziyaret edin.
Analiz amacıyla bir hedef cihazdan iş istasyonunuza veritabanı dosyası indirmek için adb pull
kullanabilirsiniz:
adb pull /data/data/<app_package_name>/databases/<db_name>.db
SQLite Tarayıcısı
Ayrıca, SQLite Tarayıcısı GUI aracını SQLite İndirilenler sayfasına yükleyebilirsiniz.
Android günlük kaydı
Android, SQLite sorgularını çarpar ve sizin için günlüğe kaydeder:
# Enable query time logging
$ adb shell setprop log.tag.SQLiteTime VERBOSE
# Disable query time logging
$ adb shell setprop log.tag.SQLiteTime ERROR
```### Perfetto tracing
### Perfetto tracing {:#perfetto-tracing}
When [configuring Perfetto](https://perfetto.dev/docs/concepts/config), you may
add the following to include tracks for individual queries:
```protobuf
data_sources {
config {
name: "linux.ftrace"
ftrace_config {
atrace_categories: "database"
}
}
}
Sizin için önerilenler
- Not: Bağlantı metni JavaScript kapalıyken gösterilir
- Sürekli Entegrasyon'da karşılaştırmalar çalıştırma
- Donmuş kare
- Makrobenchmark olmadan Temel Profiller oluşturma ve ölçme